Compare commits

..

6 Commits

711 changed files with 93471 additions and 67370 deletions

View File

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

View File

@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "jikimo_account_process",
'summary': """
处理会计凭证生成重复名称报错问题
""",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'account', 'l10n_cn'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
# 'views/views.xml',
# 'views/templates.xml',
],
# only loaded in demonstration mode
'demo': [
# 'demo/demo.xml',
],
}

View File

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

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# from odoo import http
# class JikimoAccountProcess(http.Controller):
# @http.route('/jikimo_account_process/jikimo_account_process', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/jikimo_account_process/jikimo_account_process/objects', auth='public')
# def list(self, **kw):
# return http.request.render('jikimo_account_process.listing', {
# 'root': '/jikimo_account_process/jikimo_account_process',
# 'objects': http.request.env['jikimo_account_process.jikimo_account_process'].search([]),
# })
# @http.route('/jikimo_account_process/jikimo_account_process/objects/<model("jikimo_account_process.jikimo_account_process"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('jikimo_account_process.object', {
# 'object': obj
# })

View File

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

View File

@@ -1,23 +0,0 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class CustomAccountMoveLine(models.Model):
_inherit = 'account.move'
_description = "account move line"
fapiao = fields.Char(string='发票号', size=20, copy=False, tracking=True, required=True)
@api.constrains('fapiao')
def _check_fapiao(self):
for record in self:
if record.fapiao and (len(record.fapiao) != 20 or not record.fapiao.isdecimal()):
raise ValidationError(_("Fapiao number is an 20-digit number. Please enter a correct one."))
@api.model_create_multi
def create(self, vals):
for val in vals:
val['name'] = self.env['ir.sequence'].next_by_code('account.move') or '/'
# 因为供应商与客户支付创建流程是先创建move line在修改来填充account_payment与move line的关联
return super(CustomAccountMoveLine, self).create(vals)

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# from odoo import models, fields, api
# class jikimo_account_process(models.Model):
# _name = 'jikimo_account_process.jikimo_account_process'
# _description = 'jikimo_account_process.jikimo_account_process'
# name = fields.Char()
# value = fields.Integer()
# value2 = fields.Float(compute="_value_pc", store=True)
# description = fields.Text()
#
# @api.depends('value')
# def _value_pc(self):
# for record in self:
# record.value2 = float(record.value) / 100

View File

@@ -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',

View File

@@ -6,6 +6,6 @@ import { patch } from "web.utils";
patch(WebClient.prototype, "kolpolok_custom_title_and_favicon.WebClient", {
setup() {
this._super();
// this.title.setParts({ zopenerp: "JIKIMO" });
this.title.setParts({ zopenerp: "JIKIMO" });
},
});

View File

@@ -1,8 +1,3 @@
.o_list_renderer .o_list_table tbody > tr > td:not(.o_list_record_selector):not(.o_handle_cell):not(.o_list_button):not(.o_list_record_remove){
border:1px solid #dee2e6 !important;
}
.custom_required_add::before{
content: '*';
color: red;
}

View File

@@ -0,0 +1,3 @@
.many2one_radio_field {
display: inline-block;
}

View File

@@ -0,0 +1,53 @@
/** @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);

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="jikimo_frontend.Many2OneRadioField" owl="1">
<div
role="radiogroup"
t-attf-class="o_{{ props.orientation }}"
t-att-aria-label="string"
>
<t t-foreach="items" t-as="item" t-key="item[0]">
<div class="form-check o_radio_item many2one_radio_field" aria-atomic="true">
<input
type="radio"
class="form-check-input o_radio_input"
t-att-checked="item[0] === value"
t-att-disabled="props.readonly"
t-att-name="id"
t-att-data-value="item[0]"
t-att-data-index="item_index"
t-att-id="`${id}_${item[0]}`"
t-on-change="() => this.onChange(item)"
/>
<label class="form-check-label o_form_label" t-att-for="`${id}_${item[0]}`" t-esc="item[1]" />
<div t-on-dblclick="onImageClick">
<t>
<img t-att-src="item[2]" width="50" height="50"/>
<div class="close" t-on-click="onCloseClick">×</div>
</t>
</div>
</div>
</t>
</div>
</t>
</templates>

View File

@@ -0,0 +1,41 @@
.zoomed {
position: fixed !important;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(10);
}
.many2many_flex {
display: flex;
}
.many2many_flex>div {
margin-right: 15px;
display: flex;
flex-direction: column;
align-items: center;
}
.many2many_flex>div>:nth-child(2) {
position: relative;
}
.close {
width: 20px;
height: 20px;
position: absolute;
top: -8.8px;
right: -8.8px;
color: #fff;
background-color: #000;
opacity: 0;
text-align: center;
line-height: 20px;
font-size: 18px;
}
.img_close {
opacity: 1;
transform: scale(0.1);
cursor: pointer;
}

View File

@@ -0,0 +1,38 @@
/** @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方法来添加一些事件监听器或者初始化一些变量
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');
}
}
MyCustomWidget.template = "jikimo_frontend.MyCustomWidget";
// MyCustomWidget.supportedTypes = ['many2many'];
registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget);

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="jikimo_frontend.MyCustomWidget" owl="1">
<div aria-atomic="true" class="many2many_flex">
<t t-foreach="items" t-as="item" t-key="item[0]">
<div>
<CheckBox
value="isSelected(item)"
disabled="props.readonly"
onChange="(ev) => this.onChange(item[0], ev)"
>
<t t-esc="item[1]"/>
</CheckBox>
<div t-on-dblclick="onImageClick">
<t>
<img t-att-src="item[2]" width="50" height="50"/>
<div class="close" t-on-click="onCloseClick">×</div>
</t>
</div>
</div>
</t>
</div>
</t>
</templates>

View File

@@ -5,10 +5,9 @@ import {patch} from '@web/core/utils/patch';
import {_t} from "@web/core/l10n/translation";
import {FormStatusIndicator} from "@web/views/form/form_status_indicator/form_status_indicator";
import {ListRenderer} from "@web/views/list/list_renderer";
// import {StatusBarField} from "@web/views/fields/statusbar/statusbar_field";
import {FormLabel} from "@web/views/form/form_label";
import { fieldVisualFeedback } from "@web/views/fields/field";
import {StatusBarField} from "@web/views/fields/statusbar/statusbar_field";
import {Field} from "@web/views/fields/field";
var Dialog = require('web.Dialog');
// var {patch} = require("web.utils") 这句话也行
@@ -52,24 +51,8 @@ const tableRequiredList = [
'product_template_id', 'product_uom_qty', 'price_unit','product_id','product_qty',
'name', 'fault_type', 'maintenance_standards', 'Period'
]
patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', {
setup() {
owl.onMounted(() => {
try {
const dom = this.__owl__.bdom.el
const buttonsDom = $(dom).find('.o_form_status_indicator_buttons ')
if (buttonsDom) {
const dom1 = buttonsDom.children('.o_form_button_save')
const dom2 = buttonsDom.children('.o_form_button_cancel')
dom1.append('保存')
dom2.append('不保存')
}
} catch (e) {
console.log(e)
}
});
},
patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', {
// 你可以重写或者添加一些方法和属性
async _onDiscardChanges() {
// var self = this;
@@ -107,16 +90,38 @@ patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', {
}
);
patch(Field.prototype, 'jikimo_frontend.Field', {
setup() {
owl.onMounted(this.setRequired);
return this._super(...arguments);
},
setRequired() {
const id = this.props.id
const isRequired = filedRequiredList[id]
if(id == 'number_of_axles') {
console.log(isRequired)
}
if(isRequired) {
let dom;
dom = $(`label[for=${id}]`)
if(isRequired.multiple && dom.length > 1) {
dom = dom.eq(-1)
dom = dom.parent().parent().next().find('label')
}
if(isRequired.noLabel) {
dom = dom.parent().parent()
}
let t = dom.html()
t = '<i class="c* r" style="color: red;margin-left: -4px">*</i>' + t
dom.html(t)
}
}
})
patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
setup(){
owl.onMounted(() => {
this.activeElement = this.uiService.activeElement;
this.setRequired()
this.listherHeaderBodyNum()
})
owl.onPatched(() => {
this.listherHeaderBodyNum()
})
return this._super(...arguments);
},
@@ -143,87 +148,52 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
} catch (e) {
console.log(e)
}
},
listherHeaderBodyNum() {
const dom = this.tableRef.el
try {
const thead = $(dom).children('thead')
const tbody = $(dom).children('tbody')
const thead_tr = thead.children().eq(0)
const tbody_tr = tbody.children().eq(0)
const thead_th_num = thead_tr.children().length
const tbody_tr_num = tbody_tr.children().length
const num = thead_th_num - tbody_tr_num
if(num == -1) {
tbody.children('tr').each(function () {
$(this).children('td').eq(0).remove()
})
}
}
})
} catch (e) {
console.log(e)
}
}
})
patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', {
get className() {
const { invalid, empty, readonly } = fieldVisualFeedback(
this.props.fieldInfo.FieldComponent,
this.props.record,
this.props.fieldName,
this.props.fieldInfo
);
const classes = this.props.className ? [this.props.className] : [];
const otherRequired = filedRequiredList[this.props.fieldName]
if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0 || otherRequired) {
classes.push('custom_required_add')
}
if (invalid) {
classes.push("o_field_invalid");
}
if (empty) {
classes.push("o_form_label_empty");
}
if (readonly) {
classes.push("o_form_label_readonly");
}
return classes.join(" ");
}
})
// 根据进度条设置水印
// const statusbar_params = {
// '已完工': 'bg-primary',
// '完成': 'bg-primary',
// '采购订单': 'bg-primary',
// '作废': 'bg-danger',
// '封存(报废)': 'bg-danger',
// }
// patch(StatusBarField.prototype, 'jikimo_frontend.StatusBarField', {
// setup() {
// owl.onMounted(this.ribbons);
// return this._super(...arguments);
// },
// ribbons() {
// try {
// const dom = $('.o_form_sheet.position-relative')
// const status = statusbar_params[this.currentName]
// if(status && dom.length) {
// dom.prepend(`<div class="o_widget o_widget_web_ribbon">
// <div class="ribbon ribbon-top-right">
// <span class="bg-opacity-75 ${status}" title="">${this.currentName}</span>
// </div>
// </div>`)
// }
// } catch (e) {
// console.log(e)
// }
// }
// })
const statusbar_params = {
'已完工': 'bg-primary',
'完成': 'bg-primary',
'采购订单': 'bg-primary',
'作废': 'bg-danger',
'封存(报废)': 'bg-danger',
}
patch(StatusBarField.prototype, 'jikimo_frontend.StatusBarField', {
setup() {
owl.onMounted(this.ribbons);
return this._super(...arguments);
},
ribbons() {
try {
const dom = $('.o_form_sheet.position-relative')
const status = statusbar_params[this.currentName]
if(status && dom.length) {
dom.prepend(`<div class="o_widget o_widget_web_ribbon">
<div class="ribbon ribbon-top-right">
<span class="bg-opacity-75 ${status}" title="">${this.currentName}</span>
</div>
</div>`)
}
} catch (e) {
console.log(e)
}
}
})
$(function () {
document.addEventListener('click', function () {
const dom = $('.o_form_status_indicator_buttons ')
if (dom) {
const dom1 = dom.children().eq(0)
const dom2 = dom.children().eq(1)
if (!dom1.text()) {
dom1.append('保存')
dom2.append('取消')
}
}
})
function customRequired() {
let timer = null
@@ -231,6 +201,7 @@ $(function () {
clearInterval(timer)
timer = setInterval(() => {
timer_count++
const dom = $('.custom_required')
let tableDom = $('.table_custom_required')
if (tableDom.length) {
tableDom = tableDom.eq(0).parents('tr').children('.table_custom_required')
@@ -242,6 +213,17 @@ $(function () {
})
clearInterval(timer)
}
if (dom.length) {
dom.each(function () {
const requiredDom = $(this).parent().prev().find('label')
let t = requiredDom.html()
if (t && t.indexOf('c*') < 0) {
t = '<i class="c*" style="color: red;margin-left: -4px">*</i>' + t
}
requiredDom.html(t)
})
clearInterval(timer)
}
if (timer_count == 20) {
clearInterval(timer)
}

View File

@@ -108,10 +108,6 @@ td.o_required_modifier {
}
.color_3 {
background-color: #808080;
}
.color_4 {
background-color: rgb(255, 150, 0);
}
@@ -530,13 +526,4 @@ div:has(.o_required_modifier) > label::before {
// 设置表单页面label文本不换行
.o_form_view .o_group .o_wrap_label .o_form_label {
white-space: nowrap;
}
// 修复表格内容覆盖表头bug
.o_list_renderer .o_list_table tbody th {
position: unset;
}
// 修复搜索面板checkbox样式
.o_search_panel .form-check .form-check-label span {
position: relative;
}

View File

@@ -10,6 +10,7 @@
</t>
<!-- 暂存,同一份文件中有问题,拆分后正常工作 -->
<!-- <t t-name="og.web.ListRenderer.Rows" t-inherit="web.ListRenderer.Rows" t-inherit-mode="extension"> -->

View File

@@ -5,9 +5,9 @@
<!-- 修改页面头部图标及文字 -->
<template id="favicon_icon" inherit_id="web.layout" name="Web layout">
<!-- change the title with reliance partner -->
<!-- <xpath expr="//head//title" position="before">
<xpath expr="//head//title" position="before">
<title t-esc="'JIKIMO'"/>
</xpath> -->
</xpath>
<!-- change the default favicon icon with -->
<xpath expr="//head//link[@rel='shortcut icon']" position="replace">
<link type="image/x-icon" rel="shortcut icon" href="/jikimo_frontend/static/src/img/jikimo-logo.ico"/>
@@ -16,7 +16,7 @@
<!-- hide 登录页面 powerd by odoo 及管理数据库 -->
<template id="login_page_layout" inherit_id="web.login_layout" name="Login Page Layout">
<!-- <xpath expr="//div[@class='card-body']/div[last()]" position="replace"></xpath> -->
<xpath expr="//div[@class='card-body']//div[last()]" position="replace"></xpath>
</template>
<!-- 隐藏odoo版本信息 -->

View File

@@ -190,7 +190,7 @@ def _create(self, data_list):
# 如果该用户组被限制创建或更新操作
if rec['is_create_or_update']:
raise UserError(
_("您没有执行此操作的权限。请联系管理员"))
_("You are restricted from performing this operation. Please contact the administrator."))
else:
# 如果 'access.right' 模型不存在,可以在这里定义备选逻辑
# 例如,记录日志、发送通知或者简单地跳过这部分逻辑
@@ -301,27 +301,53 @@ def unlink(self):
# This is used to restrict the access right to unlink a record
current_model_id = self.env['ir.model'].sudo().search(
[('model', '=', self._name)]).id
access_right_rec = self.env['access.right'].sudo().search_read(
[('model_id', '=', current_model_id)], ['model_id', 'is_delete',
'groups_id'])
if access_right_rec and not self.env.is_admin():
for rec in access_right_rec:
group_name = self.env['ir.model.data'].sudo().search([
('model', '=', 'res.groups'),
('res_id', '=', rec['groups_id'][0])
]).name
module_name = self.env['ir.model.data'].sudo().search([
('model', '=', 'res.groups'),
('res_id', '=', rec['groups_id'][0])
]).module
group = module_name + "." + group_name
if self.env.user.has_group(group):
if rec['is_delete']:
raise UserError(_('You are restricted from performing this'
' operation. Please contact the'
' administrator.'))
# access_right_rec = self.env['access.right'].sudo().search_read(
# [('model_id', '=', current_model_id)], ['model_id', 'is_delete',
# 'groups_id'])
# if access_right_rec and not self.env.is_admin():
# for rec in access_right_rec:
# group_name = self.env['ir.model.data'].sudo().search([
# ('model', '=', 'res.groups'),
# ('res_id', '=', rec['groups_id'][0])
# ]).name
# module_name = self.env['ir.model.data'].sudo().search([
# ('model', '=', 'res.groups'),
# ('res_id', '=', rec['groups_id'][0])
# ]).module
# group = module_name + "." + group_name
# if self.env.user.has_group(group):
# if rec['is_delete']:
# raise UserError(_('You are restricted from performing this'
# ' operation. Please contact the'
# ' administrator.'))
# 检查 'access.right' 模型是否存在于环境中
if 'access.right' in self.env:
# current_model_id = self.env['ir.model'].sudo().search([('model', '=', self._name)]).id
access_right_rec = self.env['access.right'].sudo().search_read(
[('model_id', '=', current_model_id)], ['model_id', 'is_delete', 'groups_id']
)
if access_right_rec and not self.env.is_admin():
for rec in access_right_rec:
group_data = self.env['ir.model.data'].sudo().search_read(
[('model', '=', 'res.groups'), ('res_id', '=', rec['groups_id'][0])],
['name', 'module']
)
if group_data:
group_name = group_data[0]['name']
module_name = group_data[0]['module']
group_xml_id = f"{module_name}.{group_name}"
if self.env.user.has_group(group_xml_id) and rec['is_delete']:
raise UserError(
_('You are restricted from performing this operation. Please contact the administrator.'))
else:
# 如果 'access.right' 模型不存在,可以在这里定义备选逻辑
pass
return True
BaseModel._create = _create
# BaseModel.unlink = unlink
BaseModel.unlink = unlink

View File

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

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "机企猫 采购审批流程",
'summary': """
Short (1 phrase/line) summary of the module's purpose, used as
subtitle on modules listing or apps.openerp.com""",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['purchase', 'purchase_tier_validation', 'documents', 'purchase_request', 'account', 'purchase_order_approved'],
# always loaded
'data': [
'views/views.xml',
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
'assets': {
'web.assets_backend': [
'jikimo_purchase_tier_validation/static/src/js/ir_model_extend.js',
],
},
}

View File

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

View File

@@ -1,180 +0,0 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
class jikimo_purchase_tier_validation(models.Model):
_name = 'purchase.order'
_inherit = ['purchase.order', 'tier.validation']
_description = "采购订单"
_state_from = ["draft", "to approve", "rejected"]
_state_to = ["approved"]
_tier_validation_buttons_xpath = "/form/header/button[@id='draft_confirm'][1]"
contract_document_id = fields.Many2one('documents.document', string='合同文件')
contract_file = fields.Binary(related='contract_document_id.datas', string='合同文件内容')
contract_file_name = fields.Char(related='contract_document_id.attachment_id.name', string='文件名')
# 是否已上传合同文件
is_upload_contract_file = fields.Boolean(string='是否已上传合同文件', default=False)
def button_confirm(self):
for record in self:
if record.need_validation and not record.validation_status == 'validated':
raise ValidationError(_('请先完成审批。'))
res = super(jikimo_purchase_tier_validation, self).button_confirm()
for record in self:
if record.state == 'approved':
record.order_line._validate_analytic_distribution()
record._add_supplier_to_product()
# Deal with double validation process
if record._approval_allowed():
record.button_approve()
if record.partner_id not in record.message_partner_ids:
record.message_subscribe([record.partner_id.id])
return res
def request_validation(self):
for record in self:
# 添加通知消息
if hasattr(record, 'message_post'):
current_user = self.env.user.name
record.message_post(
body=f"<strong>{current_user}</strong> 提交审批",
message_type='notification',
subtype_xmlid='mail.mt_note'
)
res = super(jikimo_purchase_tier_validation, self).request_validation()
self.state = 'to approve'
return res
def restart_validation(self):
res = super(jikimo_purchase_tier_validation, self).restart_validation()
self.state = 'draft'
return res
def _validate_tier(self, tiers=False):
res = super(jikimo_purchase_tier_validation, self)._validate_tier(tiers)
# 检查是否所有审批都已通过
all_approved = all(
tier_review.status == 'approved'
for tier_review in self.review_ids
)
if self.review_ids and all_approved: # 确保有审批记录
self.state = 'approved'
return res
@api.model
def _get_under_validation_exceptions(self):
res = super(jikimo_purchase_tier_validation, self)._get_under_validation_exceptions()
res.append("state")
return res
# 上传合同文件
def upload_contract_file(self):
print('upload_contract_file===========================')
# self.ensure_one()
# return {
# 'name': _('上传合同文件'),
# 'type': 'ir.actions.act_window',
# 'res_model': 'ir.attachment',
# 'view_mode': 'form',
# 'view_type': 'form',
# 'target': 'new',
# 'context': {
# 'default_res_model': self._name,
# 'default_res_id': self.id,
# 'default_type': 'binary',
# 'default_mimetype': 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/jpeg,image/png',
# }
# }
self.ensure_one()
action = {
'type': 'ir.actions.act_window',
'name': _('上传合同文件'),
'res_model': 'ir.attachment.wizard', # 我们需要创建一个新的向导模型
'view_mode': 'form',
'target': 'new',
'context': {
'default_res_model': self._name,
'default_res_id': self.id,
}
}
return action
# 删除合同文件
def delete_contract_file(self):
self.ensure_one()
if self.contract_document_id:
try:
document = self.contract_document_id
# 清空关联
self.write({
'contract_document_id': False,
'contract_file': False,
'contract_file_name': False
})
# 删除文档
if document:
document.with_context(no_attachment=True).sudo().unlink()
self.is_upload_contract_file = False
# 返回视图动作来刷新当前表单
return {
'type': 'ir.actions.act_window',
'res_model': 'purchase.order',
'res_id': self.id,
'view_mode': 'form',
'view_type': 'form',
'target': 'current',
'flags': {'mode': 'readonly'},
}
except Exception as e:
_logger.error('删除合同文件时出错: %s', str(e))
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('错误'),
'message': _('删除文件时出现错误'),
'type': 'danger',
'sticky': True,
}
}
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('提示'),
'message': _('没有需要删除的合同文件'),
'type': 'warning',
'sticky': False,
}
}
# class jikimo_purchase_request(models.Model):
# _inherit = 'purchase.request'
# _description = "采购申请"
# class jikimo_account_payment(models.Model):
# _inherit = 'account.payment'
# _description = "付款单"
# class jikimo_account_move(models.Model):
# _inherit = 'account.move'
# _description = "发票账单"

View File

@@ -1,14 +0,0 @@
/** @odoo-module **/
import {registerPatch} from "@mail/model/model_core";
registerPatch({
name: "ir.model.review",
fields: {
availableWebViews: {
compute() {
return ["list", "form", "activity"];
},
},
},
});

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record model="ir.ui.view" id="tier_validation_view_approved_purchase_order_form_inherit">
<field name="name">tier_validation_view_approved_purchase_order_form_inherit</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase_order_approved.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='button_release']" position="replace">
</xpath>
</field>
</record>
<record model="ir.ui.view" id="tier_validation_view_purchase_order_form_inherit">
<field name="name">tier_validation_view_purchase_order_form_inherit</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='button_approve']" position="replace">
</xpath>
<xpath expr="//header/button[@name='button_cancel']" position="replace">
</xpath>
<xpath expr="//header/field[@name='state']" position="replace">
<field name="state" widget="statusbar" statusbar_visible="draft,sent,to approve, approved, purchase" readonly="1"/>
</xpath>
<xpath expr="//header/button[last()]" position="after">
<button name="button_cancel" states="draft,to approve,sent,purchase" string="取消" type="object" data-hotkey="x" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -1 +0,0 @@
from . import comment_wizard

View File

@@ -1,15 +0,0 @@
from odoo import models, fields
class CommentWizard(models.TransientModel):
_inherit = "comment.wizard"
def add_comment(self):
rec = self.env[self.res_model].browse(self.res_id)
self.review_ids = rec.review_ids
result = super(CommentWizard, self).add_comment()
return result

View File

@@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers
from odoo import api, SUPERUSER_ID
def _data_install(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
# 获取所有需要设置的产品模板
env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').product_variant_id.write({'active': False, 'is_bfm': True})
env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').product_variant_id.write({'active': False, 'single_manufacturing': True, 'is_bfm': True})
env.ref('jikimo_sale_multiple_supply_methods.product_template_default').product_variant_id.write({'active': False, 'is_bfm': True})
env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').product_variant_id.write({'active': False})
env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').product_variant_id.write({'active': False, 'is_bfm': True})
# 为三步制造增加规则
warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1)
product_route_id = warehouse.pbm_route_id
# 创建规则:原料存货区 -> 制造前, 坯料存货区 -> 制造前, 制造后 -> 坯料存货区, 制造后 -> 成品存货区
raw_material_location_id = env['stock.location'].search([('name', '=', '坯料存货区')], limit=1)
picking_type_production = warehouse.pbm_type_id
picking_type_stock = warehouse.sam_type_id
material_location_id = env['stock.location'].search([('name', '=', '原料存货区')], limit=1)
# 为mto增加规则
mto_route_id = env.ref('stock.route_warehouse0_mto')
# 创建规则:原料存货区 -> 外包位置, 坯料存货区 -> 外包位置
subcontracting_location_id = env.company.subcontracting_location_id
picking_type_subcontracting = warehouse.subcontracting_resupply_type_id
# 为补给外包商增加规则
resupply_route_id = warehouse.subcontracting_route_id
rules_data = [
{
'name': 'WH: 原料存货区 → 制造前',
'location_src_id': material_location_id.id,
'location_dest_id': warehouse.pbm_loc_id.id,
'route_id': product_route_id.id,
'picking_type_id': picking_type_production.id,
'action': 'pull',
'sequence': 20,
'warehouse_id': warehouse.id,
'procure_method': 'mts_else_mto',
},
{
'name': 'WH: 坯料存货区 → 制造前',
'location_src_id': raw_material_location_id.id,
'location_dest_id': warehouse.pbm_loc_id.id,
'route_id': product_route_id.id,
'picking_type_id': picking_type_production.id,
'action': 'pull',
'sequence': 21,
'warehouse_id': warehouse.id,
'procure_method': 'mts_else_mto',
},
{
'name': 'WH: 制造后 → 坯料存货区',
'location_src_id': warehouse.sam_loc_id.id,
'location_dest_id': raw_material_location_id.id,
'route_id': product_route_id.id,
'picking_type_id': picking_type_stock.id,
'action': 'push',
'sequence': 23,
},
{
'name': 'WH: 制造后 → 成品存货区',
'location_src_id': warehouse.sam_loc_id.id,
'location_dest_id': env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id,
'route_id': product_route_id.id,
'picking_type_id': picking_type_stock.id,
'action': 'push',
'sequence': 24,
},
{
'name': 'WH: 原料存货区 → 外包位置 (MTO)',
'location_src_id': material_location_id.id,
'location_dest_id': subcontracting_location_id.id,
'route_id': mto_route_id.id,
'picking_type_id': picking_type_subcontracting.id,
'action': 'pull',
'sequence': 24,
'warehouse_id': warehouse.id,
'procure_method': 'mts_else_mto',
},
{
'name': 'WH: 坯料存货区 → 外包位置 (MTO)',
'location_src_id': raw_material_location_id.id,
'location_dest_id': subcontracting_location_id.id,
'route_id': mto_route_id.id,
'picking_type_id': picking_type_subcontracting.id,
'action': 'pull',
'sequence': 25,
'warehouse_id': warehouse.id,
'procure_method': 'mts_else_mto',
},
{
'name': 'WH: 坯料存货区 → 外包位置',
'location_src_id': raw_material_location_id.id,
'location_dest_id': subcontracting_location_id.id,
'route_id': resupply_route_id.id,
'picking_type_id': picking_type_subcontracting.id,
'action': 'pull',
'sequence': 26,
'warehouse_id': warehouse.id,
'procure_method': 'make_to_stock',
}
]
# 遍历每个规则数据,执行创建或更新操作
for rule_data in rules_data:
_create_or_update_stock_rule(env, rule_data)
def _create_or_update_stock_rule(env, rule_data):
# 尝试查找现有的 stock.rule
existing_rule = env['stock.rule'].search([
('name', '=', rule_data['name']),
('route_id', '=', rule_data.get('route_id'))
], limit=1)
if existing_rule:
# 如果存在,更新现有记录
existing_rule.write(rule_data)
else:
# 如果不存在,创建新记录
env['stock.rule'].create(rule_data)
def _data_uninstall(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
warehouse = env['stock.warehouse'].search([('company_id', '=', env.company.id)], limit=1)
product_route_id = warehouse.pbm_route_id
resupply_route_id = warehouse.subcontracting_route_id
mto_route_id = env.ref('stock.route_warehouse0_mto')
# Fail unlink means that the route is used somewhere (e.g. route_id on stock.rule). In this case
# we don't try to do anything.
try:
with env.cr.savepoint():
env['stock.rule'].search([('name', 'in', ('WH: 原料存货区 → 制造前', 'WH: 坯料存货区 → 制造前', 'WH: 制造后 → 坯料存货区', 'WH: 制造后 → 成品存货区')), ('route_id', '=', product_route_id.id)]).unlink()
env['stock.rule'].search([('name', 'in', ('WH: 原料存货区 → 外包位置 (MTO)', 'WH: 坯料存货区 → 外包位置 (MTO)')), ('route_id', '=', mto_route_id.id)]).unlink()
env['stock.rule'].search([('name', '=', 'WH: 坯料存货区 → 外包位置'), ('route_id', '=', resupply_route_id.id)]).unlink()
except:
pass

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': '机企猫 多供货方式',
'version': '16.0.1.0.0',
'summary': """ 报价单提供(自动化产线加工/人工线下加工/外购/委外加工)多种供货方式选择 """,
'author': 'fox',
'website': '',
'category': '',
'depends': ['sf_dlm', 'sale_stock', 'sf_sale', 'sale'],
"data": [
'security/ir.model.access.csv',
'data/stock_routes.xml',
'data/product_data.xml',
# 'views/product_product_views.xml',
],'assets': {
# 'web.assets_backend': [
# 'jikimo_sale_multiple_supply_methods/static/src/**/*'
# ],
},
'post_init_hook': '_data_install',
'uninstall_hook': '_data_uninstall',
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@@ -1 +0,0 @@
# -*- coding: utf-8 -*-

View File

@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="product_template_manual_processing" model="product.template">
<field name="name">人工线下加工模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('mrp.route_warehouse0_manufacture')]"/>
<field name="invoice_policy">delivery</field>
<field name="detailed_type">product</field>
<field name="purchase_ok">false</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<field name="single_manufacturing">true</field>
<field name="tracking">serial</field>
<!-- <field name="categ_type">成品</field> -->
<field name="is_manual_processing">true</field>
</record>
<record id="product_template_purchase" model="product.template">
<field name="name">成品外购模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('purchase_stock.route_warehouse0_buy')]"/>
<field name="tracking">serial</field>
<field name="detailed_type">product</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<!-- <field name="categ_type">成品</field> -->
</record>
<record id="product_template_outsourcing" model="product.template">
<field name="name">成品委外加工模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
<field name="route_ids"
eval="[ref('stock.route_warehouse0_mto'), ref('purchase_stock.route_warehouse0_buy'), ref('mrp_subcontracting.route_resupply_subcontractor_mto')]"/>
<field name="tracking">serial</field>
<field name="detailed_type">product</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<!-- <field name="categ_type">成品</field> -->
</record>
<record id="product_template_default" model="product.template">
<field name="name">成品初始化模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="sf_dlm.product_category_finished_sf"/>
<field name="route_ids" eval="[]"/>
<field name="tracking">serial</field>
<field name="detailed_type">product</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<!-- <field name="categ_type">成品</field> -->
</record>
<!-- 供应商信息业务平台由于数据是python创建只能指定ID -->
<record id="product_supplierinfo_bfm" model="product.supplierinfo">
<field name="partner_id" eval="91"/>
</record>
<record id="product_template_embryo_customer_provided" model="product.template">
<field name="name">坯料客供料模板</field>
<field name="active" eval="False"/>
<field name="categ_id" ref="sf_dlm.product_category_embryo_sf"/>
<field name="route_ids" eval="[
ref('stock.route_warehouse0_mto'),
ref('mrp_subcontracting.route_resupply_subcontractor_mto'),
ref('jikimo_sale_multiple_supply_methods.route_material_processing')]"/>
<field name="sale_ok">false</field>
<field name="tracking">serial</field>
<field name="detailed_type">product</field>
<field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="company_id" ref="base.main_company"/>
<!-- <field name="categ_type">坯料</field> -->
<field name="seller_ids" eval="[ref('jikimo_sale_multiple_supply_methods.product_supplierinfo_bfm')]"/>
</record>
<record id="sf_dlm.product_embryo_sf_self_machining" model="product.product">
<field name="is_manual_processing">true</field>
</record>
<record id="sf_dlm.product_embryo_sf_self_machining" model="product.product">
<field name="route_ids" eval="[(4, ref('mrp_subcontracting.route_resupply_subcontractor_mto'))]"/>
</record>
<record id="sf_dlm.product_embryo_sf_purchase" model="product.product">
<field name="route_ids" eval="[(4, ref('mrp_subcontracting.route_resupply_subcontractor_mto'))]"/>
</record>
</data>
</odoo>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="route_material_processing" model="stock.route">
<field name="name">带料加工</field>
<field name="product_selectable">true</field>
<field name="warehouse_selectable">true</field>
<field name="warehouse_ids" eval="[ref('stock.warehouse0')]"/>
<field name="sequence">16</field>
</record>
<record id="material_picking_in" model="stock.picking.type">
<field name="name">客供料入库</field>
<field name="code">incoming</field>
<field name="active">true</field>
<field name="company_id" ref="base.main_company"/>
<field name="sequence_code">DL</field>
<field name="warehouse_id" ref="stock.warehouse0"/>
<field name="default_location_src_id" ref="stock.stock_location_customers"/>
<field name="default_location_dest_id" eval="25"/>
</record>
<record id="rule_material_receiving" model="stock.rule">
<field name="name">带料收货</field>
<field name="route_id" ref="route_material_processing"/>
<field name="location_dest_id" ref="stock.stock_location_company"/>
<field name="location_src_id" ref="stock.stock_location_customers"/>
<field name="picking_type_id" ref="material_picking_in"/>
<field name="action">pull</field>
</record>
</data>
</odoo>

View File

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

View File

@@ -1,13 +0,0 @@
from odoo import models, fields
class MrpBom(models.Model):
_inherit = 'mrp.bom'
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type, product_type):
bom_id = super(MrpBom, self).bom_create(product, bom_type, product_type)
# 成品的供应商从模板中获取
if product_type == 'product':
bom_id.subcontractor_id = product.product_tmpl_id.seller_ids.partner_id.id
return bom_id

View File

@@ -1,26 +0,0 @@
from odoo import models, fields, api
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_manual_processing = fields.Boolean(string='人工线下加工')
is_customer_provided = fields.Boolean(string='客供料')
def copy_template(self, product_template_id):
if not isinstance(product_template_id, ProductTemplate):
raise ValueError('%s必须是ProductTemplate类型' % product_template_id)
self.route_ids = product_template_id.route_ids
self.categ_id = product_template_id.categ_id
self.invoice_policy = product_template_id.invoice_policy
self.detailed_type = product_template_id.detailed_type
self.purchase_ok = product_template_id.purchase_ok
self.uom_id = product_template_id.uom_id
self.uom_po_id = product_template_id.uom_po_id
self.company_id = product_template_id.company_id
self.single_manufacturing = product_template_id.single_manufacturing
self.tracking = product_template_id.tracking
self.is_bfm = product_template_id.is_bfm
self.is_manual_processing = product_template_id.is_manual_processing
# 复制 seller_ids
self.seller_ids = [(0, 0, {'partner_id': seller.partner_id.id, 'delay': 1.0, 'price': seller.price}) for seller in product_template_id.seller_ids]

View File

@@ -1,8 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sale_order_group_production_engineer,sale.order_group_production_engineer,sale.model_sale_order,sf_base.group_production_engineer,1,1,0,0
access_sale_order_line_group_production_engineer,sale_order_line_group_production_engineer,sale.model_sale_order_line,sf_base.group_production_engineer,1,1,0,0
access_product_product_group_production_engineer,product_product_group_production_engineer,product.model_product_product,sf_base.group_production_engineer,1,0,0,0
access_product_template_group_production_engineer,product_template_group_production_engineer,product.model_product_template,sf_base.group_production_engineer,1,0,0,0
access_stock_picking_group_production_engineer,stock_picking_group_production_engineer,stock.model_stock_picking,sf_base.group_production_engineer,1,0,0,0
access_stock_move_group_production_engineer,stock_move_group_production_engineer,stock.model_stock_move,sf_base.group_production_engineer,1,0,0,0
access_mrp_bom_group_production_engineer,mrp_bom_group_production_engineer,mrp.model_mrp_bom,sf_base.group_production_engineer,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sale_order_group_production_engineer sale.order_group_production_engineer sale.model_sale_order sf_base.group_production_engineer 1 1 0 0
3 access_sale_order_line_group_production_engineer sale_order_line_group_production_engineer sale.model_sale_order_line sf_base.group_production_engineer 1 1 0 0
4 access_product_product_group_production_engineer product_product_group_production_engineer product.model_product_product sf_base.group_production_engineer 1 0 0 0
5 access_product_template_group_production_engineer product_template_group_production_engineer product.model_product_template sf_base.group_production_engineer 1 0 0 0
6 access_stock_picking_group_production_engineer stock_picking_group_production_engineer stock.model_stock_picking sf_base.group_production_engineer 1 0 0 0
7 access_stock_move_group_production_engineer stock_move_group_production_engineer stock.model_stock_move sf_base.group_production_engineer 1 0 0 0
8 access_mrp_bom_group_production_engineer mrp_bom_group_production_engineer mrp.model_mrp_bom sf_base.group_production_engineer 1 0 0 0

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- 由于该模块不能依赖sf_dlm_management, 该功能只能在sf_dlm_management中实现并且依赖该模块-->
<record id="view_product_product_form_inherit_sf" model="ir.ui.view">
<field name="name">view.product.template.form.inherit.sf</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="sf_dlm_management.view_sale_product_template_form_inherit_sf"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='manual_quotation']" position="after">
<field name="is_customer_provided" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "jikimo_system_order",
'summary': """
系统工单""",
'description': """
用于处理针对系统的工作任务;
员工可以通过系统工单发起申请,由维护人员处理以后,填写处理结果。
""",
'author': "机企猫",
'website': "http://www.jikimo.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/master/odoo/addons/base/module/module_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base','mail'],
# always loaded
'data': [
'security/account_security.xml',
'security/ir.model.access.csv',
'wizard/order_wizard.xml',
'views/notice_user_config.xml',
'views/yizuo_system_order_view.xml',
'views/work_order_number.xml',
'views/res_config_settings_views.xml',
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
}

View File

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

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import http
# class TopSystemOrder(http.Controller):
# @http.route('/jikimo_system_order/jikimo_system_order/', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/jikimo_system_order/jikimo_system_order/objects/', auth='public')
# def list(self, **kw):
# return http.request.render('jikimo_system_order.listing', {
# 'root': '/jikimo_system_order/jikimo_system_order',
# 'objects': http.request.env['jikimo_system_order.jikimo_system_order'].search([]),
# })
# @http.route('/jikimo_system_order/jikimo_system_order/objects/<model("jikimo_system_order.jikimo_system_order"):obj>/', auth='public')
# def object(self, obj, **kw):
# return http.request.render('jikimo_system_order.object', {
# 'object': obj
# })

View File

@@ -1,30 +0,0 @@
<odoo>
<data>
<!-- -->
<!-- <record id="object0" model="jikimo_system_order.jikimo_system_order"> -->
<!-- <field name="name">Object 0</field> -->
<!-- <field name="value">0</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object1" model="jikimo_system_order.jikimo_system_order"> -->
<!-- <field name="name">Object 1</field> -->
<!-- <field name="value">10</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object2" model="jikimo_system_order.jikimo_system_order"> -->
<!-- <field name="name">Object 2</field> -->
<!-- <field name="value">20</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object3" model="jikimo_system_order.jikimo_system_order"> -->
<!-- <field name="name">Object 3</field> -->
<!-- <field name="value">30</field> -->
<!-- </record> -->
<!-- -->
<!-- <record id="object4" model="jikimo_system_order.jikimo_system_order"> -->
<!-- <field name="name">Object 4</field> -->
<!-- <field name="value">40</field> -->
<!-- </record> -->
<!-- -->
</data>
</odoo>

View File

@@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
from . import constant
from . import order_classify
from . import system_work_order
from . import work_order_template
from . import res_config_setting

View File

@@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
# 工单状态
STATE_SELECTION = [('draft', u'草稿'), ('unconfirmed', u'待确认'), ('pending', u'待处理'),
('processed', u'已处理待评分'), ('completed', u'已完成'), ('closed', u'已关闭')]
GRADE = [('1', '1非常不满意'), ('2', '2不满意'), ('3', '3一般'), ('4', '4满意'), ('5', '5非常满意')]

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class OrderClassify(models.Model):
_name = 'order.classify'
_order = 'sequence, name'
@api.constrains('name')
def check_base_name(self):
"""类型名称唯一"""
name_obj = self.env['order.classify'].search([('name', '=', self.name)])
if len(name_obj) >= 2:
raise ValidationError(u'该类型已存在')
# 名称
name = fields.Char(string=u'名称', size=20)
# 排序
sequence = fields.Integer(default=10)
# 是否有效
state = fields.Boolean(default=True, string='是否有效')

View File

@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from odoo import api, fields, models, _
_logger = logging.getLogger(__name__)
class ResModelWeConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
lost_agent_id = fields.Char('企微通知应用ID')
@api.model
def get_values(self):
"""
重载获取参数的方法,参数都存在系统参数中
:return:
"""
values = super(ResModelWeConfigSettings, self).get_values()
config = self.env['ir.config_parameter'].sudo()
lost_agent_id = config.get_param('lost_agent_id', default='')
values.update(
lost_agent_id=lost_agent_id,
)
return values
def set_values(self):
super(ResModelWeConfigSettings, self).set_values()
ir_config = self.env['ir.config_parameter'].sudo()
ir_config.set_param("lost_agent_id", self.lost_agent_id or "")

View File

@@ -1,183 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import ValidationError
from odoo import exceptions
from .constant import STATE_SELECTION, GRADE
import datetime
import logging
class SystemWorkOrder(models.Model):
_name = 'system.work.order'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date desc'
_description = u'系统工单'
_rec_name = 'order_number'
def get_is_technicist(self):
self._cr.execute(
"select u.id from res_users u left join res_groups_users_rel r on r.uid = u.id where r.gid in (select g.id from res_groups g where g.name = '技术员权限') and u.id ='%s'",
(self.env.user.id,))
hr = self._cr.dictfetchall()
if len(hr) > 0:
return True
else:
return False
# def get_user_department_id(self):
# """根据用户id系统员工id"""
# employee = self.env['hr.employee'].sudo().search([('user_id', '=', self.env.uid)], limit=1)
# if employee:
# if len(employee) > 0:
# if not employee.department_id:
# raise exceptions.Warning(u'您当前使用的用户没有所属部门')
# return employee.department_id
# else:
# return False
# else:
# raise exceptions.Warning(u'您当前使用的用户没有关联员工')
@api.onchange('order_template_id')
def get_title(self):
"""选择模板自动填充"""
if self.order_template_id:
self.title = self.order_template_id.title_template
self.text = self.order_template_id.text_template
# 工单编号
order_number = fields.Char(string=u'工单编号', default='/')
# 紧急程度
urgency_degree = fields.Selection([('0', u'0星'), ('1', u'一星'), ('2', u'二星'), ('3', u'三星'), ('4', u'四星'),
('5', u'五星')], string=u'紧急程度', help='五星为最紧急!', default='5')
# 工单分类(可以配置,并调整优先级)
order_type = fields.Many2one('order.classify', string=u'工单分类', domain=[('state', '=', True)])
# 发起人所属公司(res.company)
initiator_company_id = fields.Many2one('res.company', string=u'发起人所属公司', default=lambda self: self.env.user.company_id)
# 发起人部门(hr.department)
# initiator_department_id = fields.Many2one('hr.department', string=u'发起人部门', default=get_user_department_id)
# 发起人(hr.employee)
initiator_id = fields.Many2one('res.users', string=u'发起人', default=lambda self: self.env.user)
# 发起时间
date = fields.Datetime(string=u'发起时间', default=lambda self: fields.datetime.now())
# 确认人
confirm_id = fields.Many2one('res.users', string=u'确认人')
# 确认日期
confirmation_date = fields.Datetime(string=u'确认时间')
# 模板
order_template_id = fields.Many2one('work.order.template', string=u'模板', domain=[('state', '=', True)])
# 标题
title = fields.Char(string=u'标题')
# 正文
text = fields.Html(string=u'正文')
# 状态[草稿\待确认\待处理\已处理\已关闭]
state = fields.Selection(STATE_SELECTION, default='draft', string=u'状态')
# 关闭原因
close_cause = fields.Text(string=u'关闭问题原因')
# 关闭时间
close_time = fields.Datetime(string=u'关闭问题时间')
# 关闭人
close_user_id = fields.Many2one('res.users', string=u'关闭人')
# 解决人
solve_people_id = fields.Many2one('res.users', string=u'解决人')
# 用户实际问题
users_problem = fields.Text(string=u'用户实际问题')
# 最终解决方案
solution = fields.Text(string=u'最终解决方案')
# 判断是否为技术人员
# is_technicist = fields.Boolean(string=u'是否为技术人员', default=get_is_technicist)
# 打分
grade = fields.Selection(GRADE, string=u'评分')
# 评价按钮的显示
is_display = fields.Boolean('控制显示评价按钮', compute='compute_is_display')
def compute_is_display(self):
for item in self:
if item.state == 'processed' and self.env.user.id == item.initiator_id.id:
item.is_display = True
else:
item.is_display = False
@api.onchange('order_type')
def _onchange_order_type(self):
self.order_template_id = None
self.title = None
self.text = None
@api.model
def create(self, vals):
# 创建编号
if vals.get('order_number', '/') == '/':
vals['order_number'] = self.env['ir.sequence'].get('system.work.order') or '/'
return super(SystemWorkOrder, self).create(vals)
def do_draft(self, order=None):
"""状态草稿"""
bill = self
if order:
bill = order
if bill.state == 'unconfirmed':
state_remark = u'待确认 --> 草稿'
# bill.message_post(u'操作人:%s操作时间%s状态变更过程%s' % (self.env.user.name,
# (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark))
bill.state = 'draft'
def do_unconfirmed(self):
"""状态待确认"""
if self.state == 'draft':
state_remark = u'草稿 --> 待确认'
# self.message_post(u'操作人:%s操作时间%s状态变更过程%s' % (
# self.env.user.name,
# (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark))
self.state = 'unconfirmed'
# 获取通知人
objs = self.env['system.order.notice'].search([])
user_ids = objs.notice_user_ids.filtered(lambda item: item.we_employee_id not in ['', False])
we_employee_ids = user_ids.mapped('we_employee_id')
lost_agent_id = self.env['ir.config_parameter'].sudo().get_param('lost_agent_id')
wechat = self.env['we.config'].sudo().get_wechat(agent_id=lost_agent_id)
# agent_id, user_ids, content
content = """您有一张工单<font color=\"warning\">待处理</font>:**工单标题:{2}**
>创建人:{1}
>提交时间:{3}
>紧急程度:{0}
请查看工单消息,并及时处理!
""".format(self.urgency_degree,
self.initiator_id.name, self.title, (self.date + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M'))
for we_employee_id in we_employee_ids:
try:
wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_id, content=content)
except Exception as e:
logging.error('工单处理发送消息异常%s' % str(e))
return True
def do_pending(self):
"""状态待处理"""
if self.state == 'unconfirmed':
state_remark = u'待确认 --> 待处理'
# self.message_post(u'操作人:%s操作时间%s状态变更过程%s' % (
# self.env.user.name,
# (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark))
self.state = 'pending'
self.confirm_id = self.env.user
self.confirmation_date = fields.datetime.now()
return True
def urned_off(self):
"""状态关闭"""
if self.close_cause:
self.state = 'closed'
self.close_time = fields.datetime.now()
else:
raise ValidationError(u'请注明关闭原因')
return True
def unlink(self):
for item in self:
if item.state != "draft":
raise ValidationError(u'只能删除状态为【草稿】的工单。')
elif item.env.uid != item.initiator_id.id:
raise ValidationError(u'非本人不能删除')
else:
super(SystemWorkOrder, item).unlink()

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class WorkOrderTemplate(models.Model):
_name = 'work.order.template'
_order = 'num'
# 编号
num = fields.Char(string=u'编号', default='/')
# 名称
name = fields.Char(string=u'模板名称', required="1")
# 分类
work_order_type = fields.Many2one('order.classify', string=u'系统工单分类', domain=[('state', '=', True)])
# 模板标题
title_template = fields.Char(string=u'模板标题')
# 模板正文
text_template = fields.Html(string=u'模板正文')
# 模板说明
template_explain = fields.Text(string=u'模板说明')
# 是否有效
state = fields.Boolean(default=True, string=u'是否有效')
@api.model
def create(self, vals):
# 创建编号
if vals.get('num', '/') == '/':
vals['num'] = self.env['ir.sequence'].get('work.order.template') or '/'
return super(WorkOrderTemplate, self).create(vals)
class SystemOrderNotice(models.Model):
_name = 'system.order.notice'
_description = '工单处理人设置'
notice_user_ids = fields.Many2many('res.users', string='工单处理人')

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0"> <!-- noupdate表示当模块升级时是否更新本条数据-->
<!--运维权限组-->
<record id="group_operations_permissions_rwc" model="res.groups">
<field name="name">运维权限</field>
</record>
<record id="system_order_user_rule" model="ir.rule">
<field name="name">用户访问工单信息</field>
<field name="model_id" ref="model_system_work_order"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('initiator_id', '=', user.id)]</field>
</record>
<record id="system_order_group_operations_rule" model="ir.rule">
<field name="name">运维访问工单信息</field>
<field name="model_id" ref="model_system_work_order"/>
<field name="groups" eval="[(4, ref('jikimo_system_order.group_operations_permissions_rwc'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
</data>
</odoo>

View File

@@ -1,16 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
inside_system_order_classify_r,jikimo_system_order.order_classify,model_order_classify,,1,1,1,1
inside_system_work_order_rc,jikimo_system_order.system_work_order,model_system_work_order,,1,1,1,1
inside_work_order_template_r,jikimo_system_order.work_order_template,model_work_order_template,,1,1,1,1
inside_system_order_classify_rwc,jikimo_system_order.order_classify,model_order_classify,group_operations_permissions_rwc,1,1,1,0
inside_system_work_order_rwc,jikimo_system_order.system_work_order,model_system_work_order,group_operations_permissions_rwc,1,1,1,0
inside_work_order_template_rwc,jikimo_system_order.work_order_template,model_work_order_template,group_operations_permissions_rwc,1,1,1,0
order_close_wizard_group_user,jikimo_system_order.order_close_wizard,model_order_close_wizard,base.group_user,1,1,1,1
order_other_wizard_group_user,jikimo_system_order.order_other_wizard,model_order_other_wizard,base.group_user,1,1,1,1
order_technician_wizard_group_user,jikimo_system_order.order_technician_wizard,model_order_technician_wizard,base.group_user,1,1,1,1
system_work_order_wizard_group_user,jikimo_system_order.system_work_order_wizard,model_system_work_order_wizard,base.group_user,1,1,1,1
system_order_notice_group_user,jikimo_system_order.system_order_notice,model_system_order_notice,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 inside_system_order_classify_r jikimo_system_order.order_classify model_order_classify 1 1 1 1
3 inside_system_work_order_rc jikimo_system_order.system_work_order model_system_work_order 1 1 1 1
4 inside_work_order_template_r jikimo_system_order.work_order_template model_work_order_template 1 1 1 1
5 inside_system_order_classify_rwc jikimo_system_order.order_classify model_order_classify group_operations_permissions_rwc 1 1 1 0
6 inside_system_work_order_rwc jikimo_system_order.system_work_order model_system_work_order group_operations_permissions_rwc 1 1 1 0
7 inside_work_order_template_rwc jikimo_system_order.work_order_template model_work_order_template group_operations_permissions_rwc 1 1 1 0
8 order_close_wizard_group_user jikimo_system_order.order_close_wizard model_order_close_wizard base.group_user 1 1 1 1
9 order_other_wizard_group_user jikimo_system_order.order_other_wizard model_order_other_wizard base.group_user 1 1 1 1
10 order_technician_wizard_group_user jikimo_system_order.order_technician_wizard model_order_technician_wizard base.group_user 1 1 1 1
11 system_work_order_wizard_group_user jikimo_system_order.system_work_order_wizard model_system_work_order_wizard base.group_user 1 1 1 1
12 system_order_notice_group_user jikimo_system_order.system_order_notice model_system_order_notice base.group_user 1 1 1 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 533 B

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
# ---------- 工单通知处理人设置 ------------
<record model="ir.ui.view" id="tree_system_order_notice_view">
<field name="name">tree.system.order.notice</field>
<field name="model">system.order.notice</field>
<field name="arch" type="xml">
<tree string="工单处理人设置" editable="top">
<field name="notice_user_ids" widget="many2many_tags" required="1" options="{'no_create': True, 'no_edit': True}"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="search_system_order_notice_view">
<field name="name">search.system.order.notice</field>
<field name="model">system.order.notice</field>
<field name="arch" type="xml">
<search string="工单处理人设置">
<field name="notice_user_ids" string="模糊搜索"
filter_domain="[('notice_user_ids', 'ilike', self)]"/>
<separator></separator>
<field name="notice_user_ids" string="处理人"/>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_system_order_notice_view">
<field name="name">工单处理人</field>
<field name="res_model">system.order.notice</field>
<field name="view_mode">tree</field>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
[工单处理人] 还没有哦!点左上角的[创建]按钮,沙发归你了!
</p>
<p>
</p>
</field>
</record>
</data>
</odoo>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_config_settings_we_view_form_inherit" model="ir.ui.view">
<field name="name">res.config.settings.we.view.form.inherit.bpm</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('app_settings_block')]/div[6]" position="after">
<div>
<h2>企微通知应用ID</h2>
<div class="row mt16 o_settings_container" id="jd_api">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="lost_agent_id"/>
<field name="lost_agent_id"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="True">
<!-- 工单流水号 -->
<record id="seq_work_order" model="ir.sequence">
<field name="name">seq_work_order</field>
<field name="company_id"/>
<field name="code">system.work.order</field>
<field name="prefix">SO%(year)s%(month)s%(day)s</field>
<field name="padding">1</field>
</record>
<!-- 模板编号 -->
<record id="seq_order_template" model="ir.sequence">
<field name="name">seq_order_template</field>
<field name="company_id"/>
<field name="code">work.order.template</field>
<field name="prefix">TL</field>
<field name="padding">1</field>
</record>
</data>
</odoo>

View File

@@ -1,243 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--工单信息-->
<record model="ir.ui.view" id="work_order_tree">
<field name="name">工单信息</field>
<field name="model">system.work.order</field><!--对应表单名称-->
<field name="arch" type="xml">
<tree>
<field name="state" widget="badge" decoration-primary="state == 'draft'"
decoration-success="state in ('processed', 'completed')"
decoration-danger="state == 'pending'" decoration-warning="state in ('unconfirmed')"/>
<field name="order_number"/>
<field name="title"/>
<field name="initiator_id"/>
<field name="date"/>
</tree>
</field>
</record>
<!--新建系统工单-->
<record model="ir.ui.view" id="ork_order_form">
<field name="name">新建系统工单</field>
<field name="model">system.work.order</field>
<field name="arch" type="xml">
<form>
<header>
<field name="is_display" invisible="1"/>
<button string='提交' class="oe_highlight" states="draft"
type="object" name="do_unconfirmed"
attrs="{'invisible': [('state', '!=', 'draft')]}"/>
<button string='追回编辑' states="unconfirmed"
type="action" name="%(system_work_order_wizard_view_act_window)d"
context="{'explain':'确认要执行此操作吗?','object_name':'system.work.order','function_name':'do_draft','object_id':id}"/>
<button name="do_pending" states="unconfirmed"
string="确认可处理" type="object" class="oe_highlight"
groups="jikimo_system_order.group_operations_permissions_rwc"/>
<button string='处理工单' class="oe_highlight" states="pending"
type="action" name="%(launch_order_technician_wizard)d"
groups="jikimo_system_order.group_operations_permissions_rwc"/>
<button string='评价' class="oe_highlight" attrs="{'invisible': [('is_display', '=', False)]}"
type="action" name="%(launch_order_other_wizard)d" context="{'active_id':id}"/>
<button name="%(launch_order_close_wizard)d" string="关闭该工单"
attrs="{'invisible': ['|',('state', '=', 'draft'),'|',('state','=','completed'),('state','=','closed')]}"
type="action" context="{'active_id':id}"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<!-- <label for="order_number" class="oe_edit_only"/>-->
<group>
<field name="order_number" required="True" readonly="1"/>
<field name="order_type" required="True" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_create': True}"/>
<field name="date" required="True" readonly="True"/>
<field name="order_template_id" attrs="{'readonly': [('state', '!=', 'draft')]}"
domain="[('work_order_type','=',order_type),('state','=',True)]" options="{'no_create': True}"/>
<field name="confirmation_date" readonly="True"/>
<field name="urgency_degree" required="True" attrs="{'readonly': [('state','!=','draft')]}" widget="priority"/>
</group>
<group>
<field name="initiator_company_id" required="True" readonly="True"/>
<!-- <field name="initiator_department_id" required="True" readonly="True"/>-->
<field name="initiator_id" required="True" readonly="True"/>
<field name="confirm_id" readonly="True"/>
<field name="solve_people_id" readonly="True"/>
<field name="close_user_id" readonly="True"/>
</group>
<group>
<field name="title" attrs="{'readonly': [('state', '!=', 'draft')]}" required="True"/>
</group>
</group>
<notebook>
<page string="工单内容">
<field name="text" attrs="{'readonly': [('state','!=','draft')]}" required="True"/>
</page>
<page string="解决方案">
<group>
<field name="users_problem" readonly="True"/>
<field name="solution" readonly="True"/>
</group>
</page>
<page string="其他">
<group>
<field name="close_cause" readonly="True"/>
<field name="close_time" readonly="True"/>
<field name="grade" readonly="True"/>
</group>
</page>
</notebook>
</sheet>
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids" widget="mail_followers"/>-->
<!-- <field name="message_ids" widget="mail_thread"/>-->
<!-- </div>-->
</form>
</field>
</record>
<!-- 搜索工单 -->
<record model="ir.ui.view" id="restaurant_search">
<field name="name">搜索工单</field>
<field name="model">system.work.order</field>
<field name="arch" type="xml">
<search>
<field string='发起人' name="initiator_id" widget="char" required="True"/>
<field string='标题' name="title" widget="char"/>
<field string='正文' name="text" widget="html"/>
<field string='实际问题' name="users_problem" widget="text"/>
<field string='解决方案' name="solution" widget="text"/>
<filter name="today" string="今日工单" domain="[('date','=',time.strftime('%%Y-%%m-%%d'))]"/>
<filter name="yesterday" string="昨日工单"
domain="[('date', '=', (context_today() - relativedelta(days=1)).strftime('%Y-%m-%d'))]"/>
<filter name="month" string="本月工单"
domain="[('date','&gt;=', time.strftime('%Y-%m-01')),('date','&lt;', (context_today() + relativedelta(months=1)).strftime('%Y-%m-01'))]"/>
<filter name="last_month" string="上月工单"
domain="[('date','&lt;', time.strftime('%Y-%m-01')),('date','&gt;=', (context_today() - relativedelta(months=1)).strftime('%Y-%m-01'))]"/>
<filter name="unconfirmed" string="待确认" domain="[('state','=','unconfirmed')]"/>
<filter name="pending" string="待处理" domain="[('state','=','pending')]"/>
<filter name="processed" string="已处理"
domain="['|', ('state','=','processed'), ('state','=','closed')]"/>
<group>
<filter string='发起人' name="initiator_id" context='{"group_by":"initiator_id"}'/>
<filter string='工单分类' name="order_type" context='{"group_by":"order_type"}'/>
<filter string='模板' name="order_template_id" context='{"group_by":"order_template_id"}'/>
<filter string='状态' name="state" context='{"group_by":"state"}'/>
<filter string='紧急情况' name="state" context='{"group_by":"urgency_degree"}'/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="graph_tree">
<field name="name">工单图表</field>
<field name="model">system.work.order</field><!--对应表单名称-->
<field name="arch" type="xml">
<pivot>
<field name="date" type="row" interval="day"/>
<field name="order_type" type="col"/>
<field name="state" type="row"/>
</pivot>
</field>
</record>
<!-- 工单 -->
<record model="ir.actions.act_window" id="system_order">
<field name="name">工单</field>
<field name="res_model">system.work.order</field>
<field name="view_mode">tree,form,search,graph,pivot</field>
</record>
<!--工单模板信息-->
<record model="ir.ui.view" id="order_template_tree">
<field name="name">工单模板信息</field>
<field name="model">work.order.template</field><!--对应表单名称-->
<field name="arch" type="xml">
<tree>
<field name="num"/>
<field name="name"/>
<field name="work_order_type"/>
<field name="title_template"/>
<field name="template_explain"/>
<field name="state"/>
</tree>
</field>
</record>
<!--新建系统工单模板-->
<record model="ir.ui.view" id="order_template_form">
<field name="name">新建系统工单模板</field>
<field name="model">work.order.template</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="num" required="True" readonly="True"/>
<field name="name" required="True"/>
<field name="work_order_type" required="True"/>
<field name="template_explain" required="True" style="height: 50px;"/>
<field name="title_template" required="True"/>
<field name="state"/>
<field name="text_template" required="True"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- 工单模板 -->
<record model="ir.actions.act_window" id="work_template">
<field name="name">工单模板</field>
<field name="res_model">work.order.template</field>
<field name="view_mode">tree,form</field>
</record>
<!--工单分类信息-->
<record model="ir.ui.view" id="order_type_tree">
<field name="name">工单分类信息</field>
<field name="model">order.classify</field><!--对应表单名称-->
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="state"/>
</tree>
</field>
</record>
<!--新建系统分类信息-->
<record model="ir.ui.view" id="order_type_form">
<field name="name">新建系统分类信息</field>
<field name="model">order.classify</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name" required="True"/>
<field name="sequence" invisible="True"/>
<field name="state"/>
</group>
</sheet>
</form>
</field>
</record>
<!-- 工单分类 -->
<record model="ir.actions.act_window" id="classify">
<field name="name">工单分类</field>
<field name="res_model">order.classify</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/系统工单.png"/>
<menuitem name="工单" id="work_order" parent="work_order_1_list" action="system_order"/>
<menuitem name="工单模板" id="work_order_template" parent="work_order_1_list" action="work_template" groups="jikimo_system_order.group_operations_permissions_rwc"/>
<menuitem name="工单分类" id="work_order_type" parent="work_order_1_list" action="classify" groups="jikimo_system_order.group_operations_permissions_rwc"/>
<menuitem name="工单设置" id="system_order_notice_user_config" parent="work_order_1_list" action="action_system_order_notice_view" groups="jikimo_system_order.group_operations_permissions_rwc"/>
</data>
</odoo>

View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from . import order_other_wizard
from . import order_technician_wizard
from . import order_close_wizard
from . import system_work_order_wizard

View File

@@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.addons.jikimo_system_order.models.constant import STATE_SELECTION
from odoo.exceptions import ValidationError
import datetime, logging
class OrderCloseWizard(models.TransientModel):
_name = 'order.close.wizard'
def get_context(self):
if self._context.get('active_id'):
obj = self.env['system.work.order'].browse(self._context.get('active_id'))
if obj.initiator_id.id != self.env.user.id:
raise ValidationError(u'非本人无法操作')
return obj
order_id = fields.Many2one('system.work.order', string=u'工单ID',
default=lambda self: self.get_context().id)
# 关闭原因
close_cause = fields.Text(string=u'关闭问题原因', default=lambda self: self.get_context().close_cause)
# 关闭时间
close_time = fields.Datetime(string=u'关闭问题时间', default=fields.datetime.now())
# 状态
state = fields.Selection(STATE_SELECTION, default='closed', string=u'状态')
# 关闭人
close_user_id = fields.Many2one('res.users', string=u'关闭人', default=lambda self: self.env.user)
def sure(self):
self.order_id.close_cause = self.close_cause
self.order_id.close_time = self.close_time
if self.order_id.state == 'unconfirmed':
state_remark = u'待确认 --> 已关闭'
if self.order_id.state == 'pending':
state_remark = u'待处理 --> 已关闭'
if self.order_id.state == 'processed':
state_remark = u'已处理待评分 --> 已关闭'
# self.order_id.message_post(u'操作人:%s操作时间%s状态变更过程%s' % (
# self.env.user.name,
# (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark))
self.order_id.state = self.state
self.order_id.close_user_id = self.close_user_id
we_employee_ids = []
if self.order_id.initiator_id.we_employee_id:
we_employee_ids.append(self.order_id.initiator_id.we_employee_id)
lost_agent_id = self.env['ir.config_parameter'].sudo().get_param('lost_agent_id')
wechat = self.env['we.config'].sudo().get_wechat(agent_id=lost_agent_id)
# agent_id, user_ids, content
content = """您提交的工单-**工单标题:{0}**-<font color=\"#FF0000\">**已关闭**</font>
>提交时间:{1}
>处理时间:{2}
>处理人:{3}
如有问题,请联系系统管理员!
""".format(self.order_id.title,
(self.order_id.date + datetime.timedelta(hours=8)).strftime(
'%Y-%m-%d %H:%M'), (datetime.datetime.now() + datetime.timedelta(
hours=8)).strftime('%Y-%m-%d %H:%M'), self.env.user.name or '')
# wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_ids, content=content)
for we_employee_id in we_employee_ids:
try:
wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_id, content=content)
except Exception as e:
logging.error('工单关闭发送消息异常%s' % str(e))
return {}

View File

@@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import ValidationError
from odoo.addons.jikimo_system_order.models.constant import STATE_SELECTION, GRADE
import datetime
class OrderOtherWizard(models.TransientModel):
_name = 'order.other.wizard'
def get_context(self):
if self._context.get('active_id'):
obj = self.env['system.work.order'].browse(self._context.get('active_id'))
if obj.initiator_id.id != self.env.user.id:
raise ValidationError(u'非本人无法操作')
return obj
order_id = fields.Many2one('system.work.order', string=u'工单ID',
default=lambda self: self.get_context().id)
# 关闭时间
close_time = fields.Datetime(string=u'关闭时间', default=fields.datetime.now())
# 状态
state = fields.Selection(STATE_SELECTION, default='completed', string=u'状态')
# 打分
grade = fields.Selection(GRADE, string=u'评分')
# 关闭人
close_user_id = fields.Many2one('res.users', string=u'关闭人', default=lambda self: self.env.user)
def sure(self):
self.order_id.close_time = self.close_time
self.order_id.grade = self.grade
if self.order_id.state == 'processed':
state_remark = u'已处理待评分 --> 已完成'
# self.order_id.message_post(u'操作人:%s操作时间%s状态变更过程%s' % (
# self.env.user.name,
# (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark))
self.order_id.state = self.state
self.order_id.close_user_id = self.close_user_id
return {}

View File

@@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.addons.jikimo_system_order.models.constant import STATE_SELECTION
import datetime
import logging
class OrderTechnicianWizard(models.TransientModel):
_name = 'order.technician.wizard'
order_id = fields.Many2one('system.work.order', string=u'工单ID',
default=lambda self: self.env.context.get('active_id'))
# 解决人
solve_people_id = fields.Many2one('res.users', string=u'解决人', default=lambda self: self.env.user)
# 用户实际问题
users_problem = fields.Text(string=u'用户实际问题')
# 最终解决方案
solution = fields.Text(string=u'最终解决方案')
# 状态
state = fields.Selection(STATE_SELECTION, default='processed', string=u'状态')
def sure(self):
self.order_id.solve_people_id = self.solve_people_id
self.order_id.users_problem = self.users_problem
self.order_id.solution = self.solution
if self.order_id.state == 'pending':
state_remark = u'待处理 --> 已处理待评分'
# self.order_id.message_post(u'操作人:%s操作时间%s状态变更过程%s' % (
# self.env.user.name,
# (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S'), state_remark))
self.order_id.state = self.state
# 获取通知人
# objs = self.env['system.order.notice'].search([])
# user_ids = objs.notice_user_ids.filtered(lambda item: item.we_employee_id not in ['', False])
# we_employee_ids = user_ids.mapped('we_employee_id')
we_employee_ids = []
if self.order_id.initiator_id.we_employee_id:
we_employee_ids.append(self.order_id.initiator_id.we_employee_id)
print(we_employee_ids)
lost_agent_id = self.env['ir.config_parameter'].sudo().get_param('lost_agent_id')
wechat = self.env['we.config'].sudo().get_wechat(agent_id=lost_agent_id)
# agent_id, user_ids, content
content = """您提交的工单-**工单标题:{0}**-<font color=\"info\">**已处理**</font>
>提交时间:{1}
>处理反馈:{4}
>处理时间:{2}
>处理人:{3}
如有问题,请联系系统管理员!
""".format(self.order_id.title,
(self.order_id.date + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M'), (datetime.datetime.now() + datetime.timedelta(hours=8)).strftime('%Y-%m-%d %H:%M'), self.env.user.name or '', self.solution or '')
# wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_ids, content=content)
for we_employee_id in we_employee_ids:
try:
wechat.message.send_markdown(agent_id=lost_agent_id, user_ids=we_employee_id, content=content)
except Exception as e:
logging.error('工单处理发送消息异常%s' % str(e))
return {}

View File

@@ -1,122 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- 技术员向导form-->
<record model="ir.ui.view" id="wizard_technician_form_view">
<field name="name">技术员向导</field>
<field name="model">order.technician.wizard</field>
<field name="arch" type="xml">
<form string="技术员编辑">
<group>
<field name="order_id" required="1" readonly="1"/>
<field name="solve_people_id" required="1"/>
<field name="users_problem" required="1" style="height: 50px;"/>
<field name="solution" required="1" style="height: 50px;"/>
</group>
<footer>
<button name="sure" string="确定" type="object" class="oe_highlight"/>
or
<button string="取消" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="launch_order_technician_wizard">
<field name="name">技术员编辑</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">order.technician.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="wizard_technician_form_view"/>
<field name="context">{'display_default_code':False}</field>
<field name="target">new</field>
</record>
<!-- 其它向导form-->
<record model="ir.ui.view" id="wizard_other_form_view">
<field name="name">其它向导</field>
<field name="model">order.other.wizard</field>
<field name="arch" type="xml">
<form string="其它编辑">
<group>
<field name="order_id" required="1" readonly="1"/>
<field name="close_time" required="1" readonly="1"/>
<field name="grade" required="1"/>
<field name="close_user_id" required="1" readonly="1"/>
</group>
<footer>
<button name="sure" string="确定" type="object" class="oe_highlight"/>
or
<button string="取消" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="launch_order_other_wizard">
<field name="name">其它编辑</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">order.other.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="wizard_other_form_view"/>
<field name="context">{'display_default_code':False}</field>
<field name="target">new</field>
</record>
<!--关闭向导form-->
<record model="ir.ui.view" id="wizard_close_form_view">
<field name="name">关闭向导</field>
<field name="model">order.close.wizard</field>
<field name="arch" type="xml">
<form string="关闭工单">
<group>
<field name="order_id" required="1" readonly="1"/>
<field name="close_cause" required="1" style="height: 50px;"/>
<field name="close_time" required="1" readonly="1"/>
<field name="close_user_id" required="1" readonly="1"/>
</group>
<footer>
<button name="sure" string="确定" type="object" class="oe_highlight"/>
or
<button string="取消" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="launch_order_close_wizard">
<field name="name">关闭工单</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">order.close.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="wizard_close_form_view"/>
<field name="context">{'display_default_code':False}</field>
<field name="target">new</field>
</record>
<record id="system_work_order_wizard_view" model="ir.ui.view">
<field name="name">system_work_order_wizard_view</field>
<field name="model">system.work.order.wizard</field>
<field name="arch" type="xml">
<form string="二次确认">
<field name="explain" readonly="1"/>
<footer>
<button name="sure" string="确定" type="object" class="oe_highlight"/>
or
<button string="取消" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="system_work_order_wizard_view_act_window">
<field name="name">二次确认</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">system.work.order.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2017/12/12 9:46
# @Author : GuoXiang
# @Site :
# @File : system_work_order_wizard.py
# @Software: PyCharm
# @Desc :
# @license : Copyright©2018 www.dasmaster.com All Rights Reserved.
# @Contact : xg1230205321@163.com
from odoo import models, api, fields
from odoo.exceptions import ValidationError
class SystemWorkOrderWizard(models.TransientModel):
_name = "system.work.order.wizard"
_description = u"追回确认"
def _get_explain(self):
if self._context.get('object_id'):
obj = self.env['system.work.order'].browse(self._context.get('object_id'))
if obj.initiator_id.id != self.env.user.id:
raise ValidationError(u'非本人无法操作')
if self._context.get('explain'):
return self._context["explain"]
explain = fields.Char(default=_get_explain)
def sure(self):
"""
确认
:return:
"""
if self._context.get('object_id') and self._context.get('object_name') and self._context.get(
'explain') and self._context.get('function_name'):
work_sheet_obj = self.env[self._context["object_name"]].search([('id', '=', int(self._context["object_id"]))])
class_name = self._context.get('object_name') # 获得对象类名
method_name = self._context.get('function_name') # 获得对象的方法
obj_function = getattr(self.env[class_name], method_name)
obj_function(work_sheet_obj)

View File

@@ -1,3 +0,0 @@
from . import models
from . import controllers
from . import wizards

View File

@@ -1,32 +0,0 @@
{
'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',
}

View File

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

View File

@@ -1,86 +0,0 @@
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)

View File

@@ -1 +0,0 @@

View File

@@ -1,2 +0,0 @@
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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_jikimo_data_clean_wizard jikimo_test_assistant.jikimo_data_clean_wizard model_jikimo_data_clean_wizard base.group_system 1 1 1 1

View File

@@ -1,50 +0,0 @@
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;
});

View File

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

View File

@@ -1,99 +0,0 @@
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
}
}

View File

@@ -1,47 +0,0 @@
<?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>

View File

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

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫 工单异常记录',
'version': '1.0',
'summary': '记录工单的异常日志',
'sequence': 1,
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_manufacturing', 'sf_mrs_connect'],
'data': [
'views/mrp_workorder_views.xml',
'security/ir.model.access.csv',
],
'demo': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -1 +0,0 @@
from . import main

View File

@@ -1,94 +0,0 @@
from odoo import http, fields
from odoo.http import request
import json
import logging
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect
from datetime import datetime
_logger = logging.getLogger(__name__)
class WorkorderExceptionConroller(http.Controller):
@http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def workder_exception(self, **kw):
"""
记录工单异常
:param kw:
:return:
"""
_logger.info('workder_exception:%s' % kw)
try:
res = {'Succeed': True, 'ErrorCode': 0, 'Error': ''}
datas = request.httprequest.data
ret = json.loads(datas)
if not ret.get('RfidCode') or not ret.get('coding'):
res = {'Succeed': False, 'ErrorCode': 400, 'Error': '参数错误'}
return json.JSONEncoder().encode(res)
# 通过RfidCode获取就绪的CNC工单
workorder = request.env['mrp.workorder'].sudo().search([
('rfid_code', '=', ret['RfidCode']),
('routing_type', '=', 'CNC加工'),
('state', '!=', 'rework')
])
if not workorder:
res = {'Succeed': False, 'ErrorCode': 401, 'Error': '无效的工单'}
return json.JSONEncoder().encode(res)
# 创建工单异常记录,关联工单
request.env['jikimo.workorder.exception'].sudo().create({
'workorder_id': workorder.id,
'exception_code': ret.get('coding'),
'exception_content': ret.get('Error', '')
})
# 申请重新编程
workorder.production_id.update_programming_state(trigger_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
reprogramming_reason=ret.get('Error', ''))
workorder.production_id.write({'programming_state': '编程中', 'work_state': '编程中', 'is_rework': False})
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
_logger.info('workder_exception error:%s' % e)
return json.JSONEncoder().encode(res)
class SfMrsConnectController(Sf_Mrs_Connect):
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_cnc_processing_create(self, **kw):
"""
更新工单异常记录【'YC001', 'YC004'
"""
res = super(SfMrsConnectController, self).get_cnc_processing_create(**kw)
# 如果有未完成的YC0001、YC0004异常记录则标记为完成
res = json.loads(res)
_logger.info('已进入工单异常:%s' % res)
if res.get('production_ids'):
try:
productions = request.env['mrp.production'].sudo().search([('id', 'in', res.get('production_ids'))])
if productions.workorder_ids:
productions.workorder_ids.handle_exception(['YC0001', 'YC0004'])
except Exception as e:
_logger.info('更新工单异常记录失败:%s' % e)
return json.JSONEncoder().encode(res)
class ManufactruingController(Manufacturing_Connect):
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def button_Work_START(self, **kw):
"""
更新工单异常记录【'YC0002', 'YC0003'
"""
res = super(ManufactruingController, self).button_Work_START(**kw)
res = json.loads(res)
_logger.info('已进入工单异常:%s' % res)
if res.get('workorder_id'):
try:
workorder = request.env['mrp.workorder'].sudo().browse(int(res.get('workorder_id')))
workorder.handle_exception(['YC0002', 'YC0003'])
except Exception as e:
_logger.info('更新工单异常记录失败:%s' % e)
return json.JSONEncoder().encode(res)

View File

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

View File

@@ -1,14 +0,0 @@
from odoo import models, fields
class JikimoWorkorderException(models.Model):
_name = 'jikimo.workorder.exception'
_description = '工单异常记录'
_order = 'id desc'
workorder_id = fields.Many2one('mrp.workorder', string='工单')
exception_code = fields.Char('异常编码')
exception_content = fields.Char('反馈的异常/问题信息')
completion_time = fields.Datetime('处理完成时间')
state = fields.Selection([('pending', '进行中'), ('done', '已处理')], string='状态', default='pending')

View File

@@ -1,40 +0,0 @@
from odoo import models, fields
import logging
_logger = logging.getLogger(__name__)
class MrpWorkorder(models.Model):
_inherit = 'mrp.workorder'
exception_ids = fields.One2many('jikimo.workorder.exception', 'workorder_id', string='工单异常记录')
def write(self, values):
if values.get('test_results') and self.exception_ids:
pending_exception = self.exception_ids.filtered(
lambda exc: exc.state == 'pending' and exc.exception_code == 'YC0005'
)
if pending_exception:
pending_exception.write({
'completion_time': fields.Datetime.now(),
'state': 'done'
})
return super(MrpWorkorder, self).write(values)
def handle_exception(self, exception_codes):
"""
处理异常
:param exception_codes: 需要处理的异常编码列表
"""
if not isinstance(exception_codes, list):
exception_codes = [exception_codes]
if self.exception_ids:
_logger.info('workorder.exception_ids:%s' % self.exception_ids)
pending_exception = self.exception_ids.filtered(
lambda exc: exc.state == 'pending' and exc.exception_code in exception_codes
)
_logger.info('pending_exception:%s' % pending_exception)
if pending_exception:
pending_exception.write({
'completion_time': fields.Datetime.now(),
'state': 'done'
})

View File

@@ -1,5 +0,0 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_jikimo_workorder_exception","access.jikimo.workorder.exception","model_jikimo_workorder_exception","mrp.group_mrp_user",1,1,1,0
"access_jikimo_workorder_exception_group_quality","access.jikimo.workorder.exception.group_quality","model_jikimo_workorder_exception","sf_base.group_quality",1,1,1,0
"access_jikimo_workorder_exception_group_quality_director","access.jikimo.workorder.exception.group_quality_director","model_jikimo_workorder_exception","sf_base.group_quality_director",1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_jikimo_workorder_exception access.jikimo.workorder.exception model_jikimo_workorder_exception mrp.group_mrp_user 1 1 1 0
3 access_jikimo_workorder_exception_group_quality access.jikimo.workorder.exception.group_quality model_jikimo_workorder_exception sf_base.group_quality 1 1 1 0
4 access_jikimo_workorder_exception_group_quality_director access.jikimo.workorder.exception.group_quality_director model_jikimo_workorder_exception sf_base.group_quality_director 1 1 1 0

View File

@@ -1,2 +0,0 @@
from . import common
from . import test_jikimo_workorder_exception

View File

@@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, Command
from odoo.tests.common import TransactionCase, HttpCase, tagged, Form
import json
import time
import base64
from lxml import etree
@tagged('post_install', '-at_install')
class TestJikimoWorkorderExceptionCommon(TransactionCase):
def setUp(self):
super(TestJikimoWorkorderExceptionCommon, self).setUp()
# 获取名字为“1#自动生产线”的制造中心
workcenter = self.env['mrp.workcenter'].search([('name', '=', '1#自动生产线')], limit=1)
# 创建一个产品
product_product = self.env['product.product'].create({
'name': '测试产品',
'type': 'product',
})
uom_unit = self.env.ref('uom.product_uom_unit')
# 创建一个bom
self.bom = self.env['mrp.bom'].create({
'product_id': product_product.id,
'product_tmpl_id': product_product.product_tmpl_id.id,
'product_uom_id': uom_unit.id,
'product_qty': 1.0,
'type': 'normal',
})
# 创建一个制造订单
self.production = self.env['mrp.production'].create({
'name': 'Test Production',
'product_id': product_product.id,
'bom_id': self.bom.id,
'company_id': self.env.ref('base.main_company').id,
})
# 创建一个测试工单
self.workorder = self.env['mrp.workorder'].create({
'name': 'Test order',
'workcenter_id': workcenter.id,
'product_uom_id': self.bom.product_uom_id.id,
'production_id': self.production.id,
'duration_expected': 1.0,
'rfid_code': 'test-123456',
'routing_type': 'CNC加工'
})

View File

@@ -1,53 +0,0 @@
import json
from datetime import datetime
from odoo.addons.jikimo_workorder_exception.tests.common import TestJikimoWorkorderExceptionCommon
class TestJikimoWorkorderException(TestJikimoWorkorderExceptionCommon):
def test_create_exception_record(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0001',
'exception_content': '无CNC编程'
})
self.assertTrue(exception_record)
self.assertEqual(exception_record.exception_content, '无CNC编程')
self.assertEqual(exception_record.workorder_id.id, self.workorder.id)
self.assertEqual(exception_record.exception_code, 'YC0001')
def test_handle_exception(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0001',
'exception_content': '无CNC编程'
})
self.workorder.handle_exception('YC0001')
self.assertEqual(exception_record.state, 'done')
# 判断完成时间是否为当前分钟
self.assertEqual(exception_record.completion_time.minute, datetime.now().minute)
def test_handle_exception_with_invalid_code(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0001',
'exception_content': '无CNC编程'
})
self.workorder.handle_exception(['YC0002', 'YC0004'])
self.assertEqual(exception_record.state, 'pending')
self.assertEqual(exception_record.completion_time, False)
def test_handle_exception_with_test_results(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0005',
'exception_content': '工单加工失败'
})
self.workorder.write({
'test_results': '返工',
'reason': 'cutter',
'detailed_reason': '刀坏了',
})
self.assertEqual(exception_record.state, 'done')
self.assertEqual(exception_record.completion_time.minute, datetime.now().minute)

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="jikimo_workorder_exception_form_view_inherit" model="ir.ui.view">
<field name="name">mrp.workorder.form</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml">
<xpath expr="//notebook/page[last()]" position="after">
<field name="routing_type" invisible="1"/>
<page string="异常记录" name="workorder_exception" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "ER")]}'>
<field name="exception_ids" nolabel="1" readonly="1">
<tree create="false" delete="false" edit="false">
<field name="exception_content" string="反馈的异常/问题信息"/>
<field name="create_date" string="时间"/>
<field name="completion_time"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
</data>
</odoo>

View File

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

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫 工单异常消息通知',
'version': '1.0',
'summary': '当产生工单异常时,发送消息通知',
'sequence': 1,
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['jikimo_workorder_exception', 'jikimo_message_notify'],
'data': [
'data/bussiness_node.xml',
'data/template_data.xml',
# 'security/ir.model.access.csv',
],
'demo': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,
}

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="bussiness_no_functional_tool" model="jikimo.message.bussiness.node">
<field name="name">无功能刀具</field>
<field name="model">jikimo.workorder.exception</field>
</record>
<record id="bussiness_no_position_data" model="jikimo.message.bussiness.node">
<field name="name">无定位数据</field>
<field name="model">jikimo.workorder.exception</field>
</record>
<record id="bussiness_processing_failure" model="jikimo.message.bussiness.node">
<field name="name">加工失败</field>
<field name="model">jikimo.workorder.exception</field>
</record>
</data>
</odoo>

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="template_no_function_tool" model="jikimo.message.template">
<field name="name">生产线无功能刀具提醒</field>
<field name="model_id" ref="jikimo_workorder_exception_notify.model_jikimo_workorder_exception"/>
<field name="model">jikimo.workorder.exception</field>
<field name="bussiness_node_id" ref="bussiness_no_functional_tool"/>
<field name="msgtype">markdown</field>
<field name="urgency">urgent</field>
<field name="content">### 生产线无功能刀具提醒
单号:工单[{{workorder_id.production_id.name}}]({{url}})
原因:生产线无加工程序要用的功能刀具</field>
</record>
<record id="template_no_position_data" model="jikimo.message.template">
<field name="name">工单无定位数据提醒</field>
<field name="model_id" ref="jikimo_workorder_exception_notify.model_jikimo_workorder_exception"/>
<field name="model">jikimo.workorder.exception</field>
<field name="bussiness_node_id" ref="bussiness_no_position_data"/>
<field name="msgtype">markdown</field>
<field name="urgency">urgent</field>
<field name="content">### 工单无定位数据提醒
单号:工单[{{workorder_id.production_id.name}}]({{url}})
原因:无装夹定位测量数据</field>
</record>
<record id="template_processing_failure" model="jikimo.message.template">
<field name="name">工单加工失败提醒</field>
<field name="model_id" ref="jikimo_workorder_exception_notify.model_jikimo_workorder_exception"/>
<field name="model">jikimo.workorder.exception</field>
<field name="bussiness_node_id" ref="bussiness_processing_failure"/>
<field name="msgtype">markdown</field>
<field name="urgency">urgent</field>
<field name="content">### 工单加工失败提醒
单号:工单[{{workorder_id.production_id.name}}]({{url}})
原因:加工失败,工件下产线处理</field>
</record>
</data>
</odoo>

View File

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

View File

@@ -1,10 +0,0 @@
from odoo import models
class JikimoMessageTemplate(models.Model):
_inherit = "jikimo.message.template"
def _get_message_model(self):
res = super(JikimoMessageTemplate, self)._get_message_model()
res.append('jikimo.workorder.exception')
return res

View File

@@ -1,61 +0,0 @@
from odoo import models, api
from odoo.addons.sf_base.commons.common import Common
import requests, logging
_logger = logging.getLogger(__name__)
class JikimoWorkorderException(models.Model):
_name = 'jikimo.workorder.exception'
_inherit = ['jikimo.workorder.exception', 'jikimo.message.dispatch']
@api.model_create_multi
def create(self, vals_list):
res = super(JikimoWorkorderException, self).create(vals_list)
# 根据异常编码发送消息提醒
try:
for rec in res:
if rec.exception_code == 'YC0001':
# 无CNC程序调用cloud接口
data = {'name': rec.workorder_id.production_id.programming_no, 'exception_code': 'YC0001'}
configsettings = self.env['res.config.settings'].sudo().get_values()
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
url = '/api/message/workorder_exception'
config_url = configsettings['sf_url'] + url
data['token'] = configsettings['token']
ret = requests.post(config_url, json=data, headers=config_header)
ret = ret.json()
_logger.info('无CNC程序异常消息推送接口:%s' % ret)
elif rec.exception_code == 'YC0002':
# 无功能刀具
rec.add_queue('无功能刀具')
elif rec.exception_code == 'YC0003':
# 无定位数据
rec.add_queue('无定位数据')
elif rec.exception_code == 'YC0004':
# 无FTP文件调用cloud接口
data = {'name': rec.workorder_id.production_id.programming_no, 'exception_code': 'YC0004'}
configsettings = self.env['res.config.settings'].sudo().get_values()
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
url = '/api/message/workorder_exception'
config_url = configsettings['sf_url'] + url
data['token'] = configsettings['token']
ret = requests.post(config_url, json=data, headers=config_header)
ret = ret.json()
_logger.info('无FTP文件异常消息推送接口:%s' % ret)
elif rec.exception_code == 'YC0005':
# 加工失败
rec.add_queue('加工失败')
except Exception as e:
_logger.error('异常编码发送消息提醒失败:%s' % e)
return res
def _get_message(self, message_queue_ids):
contents, _ = super(JikimoWorkorderException, self)._get_message(message_queue_ids)
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('mrp.mrp_production_action').id
for index, content in enumerate(contents):
exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id)
url = url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
contents[index] = content.replace('{{url}}', url)
return contents, message_queue_ids

View File

@@ -1,2 +0,0 @@
from . import common
from . import test_jikimo_workorder_exception_notify

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, Command
from odoo.tests.common import TransactionCase, HttpCase, tagged, Form
import json
import time
import base64
from lxml import etree
@tagged('post_install', '-at_install')
class TestJikimoWorkorderExceptionNotifyCommonNotify(TransactionCase):
def setUp(self):
super(TestJikimoWorkorderExceptionNotifyCommonNotify, self).setUp()
# 获取最后一个工单
self.workorder = self.env['mrp.workorder'].search([], order='id desc', limit=1)

View File

@@ -1,113 +0,0 @@
import json
from datetime import datetime
from odoo.addons.jikimo_workorder_exception_notify.tests.common import TestJikimoWorkorderExceptionNotifyCommonNotify
class TestJikimoWorkorderExceptionNotify(TestJikimoWorkorderExceptionNotifyCommonNotify):
def test_create_message_template(self):
self.assertTrue(self.env['jikimo.message.template'].search([
('name', '=', '生产线无功能刀具提醒'),
('model', '=', 'jikimo.workorder.exception')
]))
self.assertTrue(self.env['jikimo.message.template'].search([
('name', '=', '工单无定位数据提醒'),
('model', '=', 'jikimo.workorder.exception')
]))
self.assertTrue(self.env['jikimo.message.template'].search([
('name', '=', '工单加工失败提醒'),
('model', '=', 'jikimo.workorder.exception')
]))
def test_create_message_queue_yc0001(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0001',
'exception_content': '无CNC程序'
})
message_record = self.env['jikimo.message.queue'].search([
('res_id', '=', exception_record.id),
('model', '=', 'jikimo.workorder.exception'),
('message_status', '=', 'pending')
])
self.assertFalse(message_record)
def test_create_message_queue_yc0002(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0002',
'exception_content': '无功能刀具'
})
bussiness_node = self.env['jikimo.message.bussiness.node'].search([
('name', '=', '无功能刀具'),
('model', '=', 'jikimo.workorder.exception')
])
message_template = self.env['jikimo.message.template'].search([
('bussiness_node_id', '=', bussiness_node.id),
('model', '=', 'jikimo.workorder.exception')
])
message_record = self.env['jikimo.message.queue'].search([
('res_id', '=', exception_record.id),
('model', '=', 'jikimo.workorder.exception'),
('message_status', '=', 'pending'),
('message_template_id', '=', message_template.id)
])
self.assertTrue(message_record)
def test_create_message_queue_yc0003(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0003',
'exception_content': '无定位数据'
})
bussiness_node = self.env['jikimo.message.bussiness.node'].search([
('name', '=', '无定位数据'),
('model', '=', 'jikimo.workorder.exception')
])
message_template = self.env['jikimo.message.template'].search([
('bussiness_node_id', '=', bussiness_node.id),
('model', '=', 'jikimo.workorder.exception')
])
message_record = self.env['jikimo.message.queue'].search([
('res_id', '=', exception_record.id),
('model', '=', 'jikimo.workorder.exception'),
('message_status', '=', 'pending'),
('message_template_id', '=', message_template.id)
])
self.assertTrue(message_record)
def test_create_message_queue_yc0004(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0004',
'exception_content': '无CNC程序'
})
message_record = self.env['jikimo.message.queue'].search([
('res_id', '=', exception_record.id),
('model', '=', 'jikimo.workorder.exception'),
('message_status', '=', 'pending')
])
self.assertFalse(message_record)
def test_get_message(self):
exception_record = self.env['jikimo.workorder.exception'].create({
'workorder_id': self.workorder.id,
'exception_code': 'YC0002',
'exception_content': '无功能刀具'
})
message_queue_ids = self.env['jikimo.message.queue'].search([
('res_id', '=', exception_record.id),
('model', '=', 'jikimo.workorder.exception'),
('message_status', '=', 'pending')
])
message = self.env['jikimo.workorder.exception']._get_message(message_queue_ids)
self.assertTrue(message)

View File

@@ -1273,18 +1273,3 @@ msgstr ""
#: model:product.template,description_sale:mrp_workorder.product_template_stool_top
msgid "wooden stool top"
msgstr ""
#. module: mrp_workorder
#: model:quality.point.test_type,name:mrp_workorder.test_type_register_consumed_materials
msgid "Register Consumed Materials"
msgstr "登记消耗材料"
#. module: mrp_workorder
#: model:quality.point.test_type,name:mrp_workorder.test_type_register_byproducts
msgid "Register By-products"
msgstr "按产品注册"
#. module: mrp_workorder
#: model:quality.point.test_type,name:mrp_workorder.test_type_print_label
msgid "Print label"
msgstr "打印标签"

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from odoo import fields, models
class MrpProduction(models.Model):
@@ -12,8 +12,7 @@ class MrpProduction(models.Model):
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
def _split_productions(self, amounts=False, cancel_remaining_qty=False, set_consumed_qty=False):
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty,
set_consumed_qty=set_consumed_qty)
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty, set_consumed_qty=set_consumed_qty)
backorders = productions[1:]
if not backorders:
return productions
@@ -21,4 +20,3 @@ class MrpProduction(models.Model):
if wo.current_quality_check_id.component_id:
wo.current_quality_check_id._update_component_quantity()
return productions

View File

@@ -27,7 +27,7 @@ class MrpWorkcenter(models.Model):
class MrpProductionWorkcenterLine(models.Model):
_name = 'mrp.workorder'
_inherit = ['mrp.workorder', 'barcodes.barcode_events_mixin', 'mail.thread', 'mail.activity.mixin']
_inherit = ['mrp.workorder', 'barcodes.barcode_events_mixin']
quality_point_ids = fields.Many2many('quality.point', compute='_compute_quality_point_ids', store=True)
quality_point_count = fields.Integer('Steps', compute='_compute_quality_point_count')
@@ -47,17 +47,14 @@ class MrpProductionWorkcenterLine(models.Model):
is_last_lot = fields.Boolean('Is Last lot', compute='_compute_is_last_lot')
is_first_started_wo = fields.Boolean('Is The first Work Order', compute='_compute_is_last_unfinished_wo')
is_last_unfinished_wo = fields.Boolean('Is Last Work Order To Process', compute='_compute_is_last_unfinished_wo',
store=False)
is_last_unfinished_wo = fields.Boolean('Is Last Work Order To Process', compute='_compute_is_last_unfinished_wo', store=False)
lot_id = fields.Many2one(related='current_quality_check_id.lot_id', readonly=False)
move_id = fields.Many2one(related='current_quality_check_id.move_id', readonly=False)
move_line_id = fields.Many2one(related='current_quality_check_id.move_line_id', readonly=False)
move_line_ids = fields.One2many(related='move_id.move_line_ids')
quality_state = fields.Selection(related='current_quality_check_id.quality_state', string="Quality State",
readonly=False)
quality_state = fields.Selection(related='current_quality_check_id.quality_state', string="Quality State", readonly=False)
qty_done = fields.Float(related='current_quality_check_id.qty_done', readonly=False)
test_type_id = fields.Many2one('quality.point.test_type', 'Test Type',
related='current_quality_check_id.test_type_id')
test_type_id = fields.Many2one('quality.point.test_type', 'Test Type', related='current_quality_check_id.test_type_id')
test_type = fields.Char(related='test_type_id.technical_name')
user_id = fields.Many2one(related='current_quality_check_id.user_id', readonly=False)
worksheet_page = fields.Integer('Worksheet page')
@@ -68,8 +65,7 @@ class MrpProductionWorkcenterLine(models.Model):
def _compute_quality_point_ids(self):
for workorder in self:
quality_points = workorder.operation_id.quality_point_ids
quality_points = quality_points.filtered(
lambda qp: not qp.product_ids or workorder.production_id.product_id in qp.product_ids)
quality_points = quality_points.filtered(lambda qp: not qp.product_ids or workorder.production_id.product_id in qp.product_ids)
workorder.quality_point_ids = quality_points
@api.depends('operation_id')
@@ -95,8 +91,7 @@ class MrpProductionWorkcenterLine(models.Model):
@api.depends('check_ids')
def _compute_finished_product_check_ids(self):
for wo in self:
wo.finished_product_check_ids = wo.check_ids.filtered(
lambda c: c.finished_product_sequence == wo.qty_produced)
wo.finished_product_check_ids = wo.check_ids.filtered(lambda c: c.finished_product_sequence == wo.qty_produced)
def write(self, values):
res = super().write(values)
@@ -143,8 +138,7 @@ class MrpProductionWorkcenterLine(models.Model):
self.finished_lot_id = self.env['stock.lot'].create({
'product_id': self.product_id.id,
'company_id': self.company_id.id,
'name': self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) or self.env[
'ir.sequence'].next_by_code('stock.lot.serial'),
'name': self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) or self.env['ir.sequence'].next_by_code('stock.lot.serial'),
})
def _create_subsequent_checks(self):
@@ -158,7 +152,7 @@ class MrpProductionWorkcenterLine(models.Model):
"""
# Create another quality check if necessary
next_check = self.current_quality_check_id.next_check_id
if next_check.component_id != self.current_quality_check_id.product_id or \
if next_check.component_id != self.current_quality_check_id.product_id or\
next_check.point_id != self.current_quality_check_id.point_id:
# TODO: manage reservation here
@@ -285,8 +279,7 @@ class MrpProductionWorkcenterLine(models.Model):
if self.current_quality_check_id:
team = self.current_quality_check_id.team_id
else:
team = self.env['quality.alert.team'].search(
['|', ('company_id', '=', self.company_id.id), ('company_id', '=', False)], limit=1)
team = self.env['quality.alert.team'].search(['|', ('company_id', '=', self.company_id.id), ('company_id', '=', False)], limit=1)
return {
'type': 'ir.actions.act_window',
'res_model': 'quality.check',
@@ -327,8 +320,7 @@ class MrpProductionWorkcenterLine(models.Model):
production = wo.production_id
move_raw_ids = wo.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
move_finished_ids = wo.move_finished_ids.filtered(
lambda m: m.state not in ('done', 'cancel') and m.product_id != wo.production_id.product_id)
move_finished_ids = wo.move_finished_ids.filtered(lambda m: m.state not in ('done', 'cancel') and m.product_id != wo.production_id.product_id)
previous_check = self.env['quality.check']
for point in wo.quality_point_ids:
# Check if we need a quality control for this point
@@ -350,13 +342,11 @@ class MrpProductionWorkcenterLine(models.Model):
if point.test_type == 'register_byproducts':
moves = move_finished_ids.filtered(lambda m: m.product_id == point.component_id)
if not moves:
moves = production.move_finished_ids.filtered(
lambda m: not m.operation_id and m.product_id == point.component_id)
moves = production.move_finished_ids.filtered(lambda m: not m.operation_id and m.product_id == point.component_id)
elif point.test_type == 'register_consumed_materials':
moves = move_raw_ids.filtered(lambda m: m.product_id == point.component_id)
if not moves:
moves = production.move_raw_ids.filtered(
lambda m: not m.operation_id and m.product_id == point.component_id)
moves = production.move_raw_ids.filtered(lambda m: not m.operation_id and m.product_id == point.component_id)
else:
check = self.env['quality.check'].create(values)
previous_check.next_check_id = check
@@ -373,10 +363,8 @@ class MrpProductionWorkcenterLine(models.Model):
processed_move |= moves
# Generate quality checks associated with unreferenced components
moves_without_check = ((move_raw_ids | move_finished_ids) - processed_move).filtered(lambda move: (
move.has_tracking != 'none' and not move.raw_material_production_id.use_auto_consume_components_lots) or move.operation_id)
quality_team_id = self.env['quality.alert.team'].search(
['|', ('company_id', '=', wo.company_id.id), ('company_id', '=', False)], limit=1).id
moves_without_check = ((move_raw_ids | move_finished_ids) - processed_move).filtered(lambda move: (move.has_tracking != 'none' and not move.raw_material_production_id.use_auto_consume_components_lots) or move.operation_id)
quality_team_id = self.env['quality.alert.team'].search(['|', ('company_id', '=', wo.company_id.id), ('company_id', '=', False)], limit=1).id
for move in moves_without_check:
values = {
'production_id': production.id,
@@ -424,8 +412,7 @@ class MrpProductionWorkcenterLine(models.Model):
backorder = False
# Trigger the backorder process if we produce less than expected
if float_compare(self.qty_producing, self.qty_remaining,
precision_rounding=self.product_uom_id.rounding) == -1 and self.is_first_started_wo:
if float_compare(self.qty_producing, self.qty_remaining, precision_rounding=self.product_uom_id.rounding) == -1 and self.is_first_started_wo:
backorder = self.production_id._split_productions()[1:]
for workorder in backorder.workorder_ids:
if workorder.product_tracking == 'serial':
@@ -436,8 +423,7 @@ class MrpProductionWorkcenterLine(models.Model):
else:
if self.operation_id:
backorder = (self.production_id.procurement_group_id.mrp_production_ids - self.production_id).filtered(
lambda p: p.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_id).state not in (
'cancel', 'done')
lambda p: p.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_id).state not in ('cancel', 'done')
)[:1]
else:
index = list(self.production_id.workorder_ids).index(self)
@@ -456,8 +442,7 @@ class MrpProductionWorkcenterLine(models.Model):
wo.current_quality_check_id._update_component_quantity()
if not self.env.context.get('no_start_next'):
if self.operation_id:
return backorder.workorder_ids.filtered(
lambda wo: wo.operation_id == self.operation_id).open_tablet_view()
return backorder.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_id).open_tablet_view()
else:
index = list(self.production_id.workorder_ids).index(self)
return backorder.workorder_ids[index].open_tablet_view()
@@ -481,8 +466,7 @@ class MrpProductionWorkcenterLine(models.Model):
def open_tablet_view(self):
self.ensure_one()
if not self.is_user_working and self.working_state != 'blocked' and self.state in (
'ready', 'waiting', 'progress', 'pending'):
if not self.is_user_working and self.working_state != 'blocked' and self.state in ('ready', 'waiting', 'progress', 'pending'):
self.button_start()
action = self.env["ir.actions.actions"]._for_xml_id("mrp_workorder.tablet_client_action")
action['target'] = 'fullscreen'
@@ -537,8 +521,7 @@ class MrpProductionWorkcenterLine(models.Model):
data = {
'mrp.workorder': self.read(self._get_fields_for_tablet(), load=False)[0],
'quality.check': self.check_ids._get_fields_for_tablet(sorted_check_list),
'operation': self.operation_id.read(self.operation_id._get_fields_for_tablet())[
0] if self.operation_id else {},
'operation': self.operation_id.read(self.operation_id._get_fields_for_tablet())[0] if self.operation_id else {},
'working_state': self.workcenter_id.working_state,
'views': {
'workorder': self.env.ref('mrp_workorder.mrp_workorder_view_form_tablet').id,
@@ -570,8 +553,7 @@ class MrpProductionWorkcenterLine(models.Model):
return {
'duration': self.duration,
'position': bisect_left(last30op, self.duration),
# which position regarded other workorders ranked by duration
'position': bisect_left(last30op, self.duration), # which position regarded other workorders ranked by duration
'quality_score': score,
'show_rainbow': show_rainbow,
}

View File

@@ -1,44 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Manufacturing Order for Planing view -->
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
<field name="name">mrp.production.tree.inherit.planning</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'"
decoration-danger="date_planned_start&lt;current_date and state not in ('done','cancel')"
decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
<field name="date_planned_start"/>
<field name="product_id"/>
<field name="product_qty" sum="Total Qty" string="Quantity"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}"
groups="uom.group_uom"/>
<field name="reservation_state" string="Availability"/>
<field name="origin"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- Manufacturing Order for Planing view -->
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
<field name="name">mrp.production.tree.inherit.planning</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'" decoration-danger="date_planned_start&lt;current_date and state not in ('done','cancel')" decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
<field name="date_planned_start"/>
<field name="product_id"/>
<field name="product_qty" sum="Total Qty" string="Quantity"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
<field name="reservation_state" string="Availability"/>
<field name="origin"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
<!-- <field name="model">mrp.production</field>-->
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
<!-- &lt;!&ndash; 这里放置替换后的内容 &ndash;&gt;-->
<!-- </xpath>-->
<!-- <xpath expr="//notebook" position="after">-->
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
<!-- <field name="model">mrp.production</field>-->
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
<!-- &lt;!&ndash; 这里放置替换后的内容 &ndash;&gt;-->
<!-- </xpath>-->
<!-- <xpath expr="//notebook" position="after">-->
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
<field name="name">mrp.production.search.view.inherit.planning</field>
@@ -46,9 +43,7 @@
<field name="inherit_id" ref="mrp.view_mrp_production_filter"/>
<field name="arch" type="xml">
<filter name="filter_planned" position="attributes">
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False),
('date_planned_finished', '!=', False)]
</attribute>
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</attribute>
</filter>
</field>
</record>
@@ -56,33 +51,30 @@
<record id="production_order_unplan_server_action" model="ir.actions.server">
<field name="name">Unplan orders</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="binding_model_id" ref="mrp.model_mrp_production"/>
<field name="binding_model_id" ref="mrp.model_mrp_production" />
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">records.button_unplan()</field>
</record>
<record id="mrp.act_product_mrp_production_workcenter" model="ir.actions.act_window">
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id),
('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]
</field>
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</field>
<field name="view_id" ref="mrp_production_tree_view_planning"/>
</record>
<menuitem id="mrp_workorder_menu_planning"
name="Work Orders"
sequence="2"
parent="mrp.mrp_planning_menu_root"
groups="mrp.group_mrp_routings"/>
name="Work Orders"
sequence="2"
parent="mrp.mrp_planning_menu_root"
groups="mrp.group_mrp_routings"/>
<menuitem id="menu_mrp_workorder_production"
name="Planning by Production"
sequence="1"
action="mrp.action_mrp_workorder_production"
parent="mrp_workorder_menu_planning"/>
name="Planning by Production"
sequence="1"
action="mrp.action_mrp_workorder_production"
parent="mrp_workorder_menu_planning"/>
<menuitem id="menu_mrp_workorder_workcenter"
name="Planning by Workcenter"
sequence="2"
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
parent="mrp_workorder_menu_planning"/>
name="Planning by Workcenter"
sequence="2"
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
parent="mrp_workorder_menu_planning"/>
</odoo>

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