sf新增表面工艺参数同步接口和基础对象,新增dockerfile和requirements.txt文件

This commit is contained in:
gqh
2023-03-14 14:56:18 +08:00
parent fddc8100d6
commit 84ef243b9f
32 changed files with 223 additions and 1262 deletions

32
Dockerfile Normal file
View File

@@ -0,0 +1,32 @@
FROM jikimo-hn-docker.pkg.coding.net/jikimo_sfs/odoo-sf/odoo-sf:1.0
USER root:root
ADD . /app
WORKDIR /app
COPY ./requirements.txt /app
RUN sed -i 's#http://deb.debian.org#http://mirrors.aliyun.com#g' /etc/apt/sources.list
# Install some deps, lessc and less-plugin-clean-css, and wkhtmltopdf
# 系统更新
RUN apt-get update
# RUN apt-get do-release-upgrade
# 更新python的依赖
#RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
# 设置时区
RUN rm -rf /etc/localtime
RUN ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 拷贝代码到工作目录
COPY . /mnt/extra-addons
# 拷贝配置文件到配置目录
COPY ./odoo.conf /etc/odoo
# 启动odoo
#COPY ./entrypoint.sh /

View File

@@ -13,7 +13,6 @@
</field> </field>
</record> </record>
</odoo> </odoo>

View File

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

View File

@@ -1,32 +0,0 @@
{
"name": "OWL开发演示",
"summary": "示例OWL开发模块。",
"description": "用于演示OWL模块的开发。",
"author": "Van",
"website": "https://topodoo.com",
"category": "Tutorials",
"version": "15.0.0.1",
"depends": ["sale", "sale_management","web_widget_model_viewer"],
"demo": [],
"data": [
#'report/test_sale_report.xml',
'views/views.xml',
],
'assets': {
'web.assets_qweb': [
# 'owl_demo/static/src/xml/MyComponent.xml',
#'owl_demo/static/src/js/html_template/template_demo_field.xml',
'owl_demo/static/src/js/3d_viewer/3d_viewer.xml',
],
'web.assets_backend': [
# 'owl_demo/static/src/xml/MyComponent.xml',
# 'owl_demo/static/src/js/components/MyComponent.js',
# 'owl_demo/static/src/js/services/*',
#'owl_demo/static/src/js/html_template/*',
'owl_demo/static/src/js/3d_viewer/*',
'owl_demo/static/src/lib/model-viewer.min.js',
],
}
}

View File

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

View File

@@ -1,266 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import io
import json
import os
from odoo import _
from odoo import http
from odoo.http import request
class Viewer3d(http.Controller):
@http.route(['/Viewer3d/<int:attachment_id>'], type='http', auth="None")
def viewer3d(self, attachment_id, **kwargs):
attachment = self._attachment_get(attachment_id)
return attachment
def _attachment_get(self, attachment_id):
attachment = request.env['ir.attachment'].sudo().browse(attachment_id)
if not attachment:
return
return attachment.datas
def stl_to_gltf(binary_stl_path, out_path, is_binary):
import struct
gltf2 = '''
{
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"buffers" : [
{
%s
"byteLength" : %d
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : %d,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : %d,
"byteLength" : %d,
"target" : 34962
}
],
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5125,
"count" : %d,
"type" : "SCALAR",
"max" : [ %d ],
"min" : [ 0 ]
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : %d,
"type" : "VEC3",
"min" : [%f, %f, %f],
"max" : [%f, %f, %f]
}
],
"asset" : {
"version" : "2.0"
}
}
'''
header_bytes = 80
unsigned_long_int_bytes = 4
float_bytes = 4
vec3_bytes = 4 * 3
spacer_bytes = 2
num_vertices_in_face = 3
vertices = {}
indices = []
if not is_binary:
out_bin = os.path.join(out_path, "out.bin")
out_gltf = os.path.join(out_path, "out.gltf")
else:
out_bin = out_path
unpack_face = struct.Struct("<12fH").unpack
face_bytes = float_bytes * 12 + 2
with open(path_to_stl, "rb") as f:
f.seek(header_bytes) # skip 80 bytes headers
num_faces_bytes = f.read(unsigned_long_int_bytes)
number_faces = struct.unpack("<I", num_faces_bytes)[0]
# the vec3_bytes is for normal
stl_assume_bytes = header_bytes + unsigned_long_int_bytes + number_faces * (
vec3_bytes * 3 + spacer_bytes + vec3_bytes)
assert stl_assume_bytes == os.path.getsize(path_to_stl), "stl is not binary or ill formatted"
minx, maxx = [9999999, -9999999]
miny, maxy = [9999999, -9999999]
minz, maxz = [9999999, -9999999]
vertices_length_counter = 0
data = struct.unpack("<" + "12fH" * number_faces, f.read())
len_data = len(data)
for i in range(0, len_data, 13):
for j in range(3, 12, 3):
x, y, z = data[i + j:i + j + 3]
x = int(x * 100000) / 100000
y = int(y * 100000) / 100000
z = int(z * 100000) / 100000
tuple_xyz = (x, y, z);
try:
indices.append(vertices[tuple_xyz])
except KeyError:
vertices[tuple_xyz] = vertices_length_counter
vertices_length_counter += 1
indices.append(vertices[tuple_xyz])
if x < minx: minx = x
if x > maxx: maxx = x
if y < miny: miny = y
if y > maxy: maxy = y
if z < minz: minz = z
if z > maxz: maxz = z
# f.seek(spacer_bytes, 1) # skip the spacer
number_vertices = len(vertices)
vertices_bytelength = number_vertices * vec3_bytes # each vec3 has 3 floats, each float is 4 bytes
unpadded_indices_bytelength = number_vertices * unsigned_long_int_bytes
out_number_vertices = len(vertices)
out_number_indices = len(indices)
unpadded_indices_bytelength = out_number_indices * unsigned_long_int_bytes
indices_bytelength = (unpadded_indices_bytelength + 3) & ~3
out_bin_bytelength = vertices_bytelength + indices_bytelength
if is_binary:
out_bin_uir = ""
else:
out_bin_uir = '"uri": "out.bin",'
gltf2 = gltf2 % (out_bin_uir,
# buffer
out_bin_bytelength,
# bufferViews[0]
indices_bytelength,
# bufferViews[1]
indices_bytelength,
vertices_bytelength,
# accessors[0]
out_number_indices,
out_number_vertices - 1,
# accessors[1]
out_number_vertices,
minx, miny, minz,
maxx, maxy, maxz
)
glb_out = bytearray()
if is_binary:
gltf2 = gltf2.replace(" ", "")
gltf2 = gltf2.replace("\n", "")
scene = bytearray(gltf2.encode())
scene_len = len(scene)
padded_scene_len = (scene_len + 3) & ~3
body_offset = padded_scene_len + 12 + 8
file_len = body_offset + out_bin_bytelength + 8
# 12-byte header
glb_out.extend(struct.pack('<I', 0x46546C67)) # magic number for glTF
glb_out.extend(struct.pack('<I', 2))
glb_out.extend(struct.pack('<I', file_len))
# chunk 0
glb_out.extend(struct.pack('<I', padded_scene_len))
glb_out.extend(struct.pack('<I', 0x4E4F534A)) # magic number for JSON
glb_out.extend(scene)
while len(glb_out) < body_offset:
glb_out.extend(b' ')
# chunk 1
glb_out.extend(struct.pack('<I', out_bin_bytelength))
glb_out.extend(struct.pack('<I', 0x004E4942)) # magin number for BIN
# print('<%dI' % len(indices))
# print(struct.pack('<%dI' % len(indices), *indices))
glb_out.extend(struct.pack('<%dI' % len(indices), *indices))
for i in range(indices_bytelength - unpadded_indices_bytelength):
glb_out.extend(b' ')
vertices = dict((v, k) for k, v in vertices.items())
# glb_out.extend(struct.pack('f',
# print([each_v for vertices[v_counter] for v_counter in range(number_vertices)]) # magin number for BIN
vertices = [vertices[i] for i in range(number_vertices)]
flatten = lambda l: [item for sublist in l for item in sublist]
# for v_counter in :
# v_3f = vertices[v_counter]
# all_floats_in_vertices.append(v_3f[0])
# all_floats_in_vertices.append(v_3f[1])
# all_floats_in_vertices.append(v_3f[2])
# for v_counter in range(number_vertices):
glb_out.extend(struct.pack('%df' % number_vertices * 3, *flatten(vertices))) # magin number for BIN
# for v_counter in range(number_vertices):
# glb_out.extend(struct.pack('3f', *vertices[v_counter])) # magin number for BIN
# for (v_x, v_y, v_z), _ in sorted(vertices.items(), key=lambda x: x[1]):
# glb_out.extend(struct.pack('3f', v_x, v_y, v_z)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_y)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_z)) # magin number for BIN
with open(out_bin, "wb") as out:
out.write(glb_out)
if not is_binary:
with open(out_gltf, "w") as out:
out.write(gltf2)

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="sale.report_saleorder_document" inherit_id="sale.report_saleorder_document">
<t t-call="web.external_layout">
<h1>hello jacker!</h1>
</t>
</template>
</odoo>

View File

@@ -1,30 +0,0 @@
.o_int_colorpicker {
.o_color_pill {
display: inline-block;
height: 25px;
width: 25px;
margin: 4px;
border-radius: 25px;
position: relative;
@for $size from 1 through length($o-colors) {
&.o_color_#{$size - 1} {
background-color: nth($o-colors, $size);
&:not(.readonly):hover {
transform: scale(1.2);
transition: 0.3s;
cursor: pointer;
}
&.active:after{
content: "\f00c";
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
color: #fff;
position: absolute;
padding: 4px;
font-size: 16px;
}
}
}
}
}

View File

@@ -1,127 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
import { FileUploader } from "@web/views/fields/file_handler";
import { session } from "@web/session";
import { useService } from "@web/core/utils/hooks";
import { isBinarySize } from "@web/core/utils/binary";
import { download } from "@web/core/network/download";
import utils from 'web.utils';
import core from 'web.core';
import rpc from 'web.rpc';
var QWeb = core.qweb;
import { Component, onWillUpdateProps, useState, useRef, useEffect } from "@odoo/owl";
export class StepViewer extends Component {
setup() {
this.notification = useService("notification");
this.state = useState({
fileName: this.props.record.data[this.props.fileNameField] || "",
});
onWillUpdateProps((nextProps) => {
if (nextProps.readonly) {
this.state.fileName = nextProps.record.data[nextProps.fileNameField] || "";
}
});
this.show_template = useRef('showStepViewer')
useInputField({
getValue: () => this.props.value || ""
});
useEffect(
(el) => {
if (!el) {
return;
}
return this.formatTemplate($(el));
},
() => [this.show_template.el],
)
}
formatTemplate($el) {
console.log($el)
var self=this;
if (this.props.readonly && this.props.value) {
var url = "/owl_demo/static/src/js/3d_viewer/test.glb";
// var session = this.getSession();
// if (this.props.value) {
// if (utils.is_bin_size(this.props.value)) {
// url = session.url("/web/content", {
// model: this.model,
// id: JSON.stringify(this.props.record.res_id),
// field: this.props.record.name,
// });
// } else {
// url = "data:model/gltf-binary;base64," + this.propsvalue.value;
// }
// }
return $el.html(QWeb.render("owl_demo.StepViewer",{'url':url}))
}
}
get fileName() {
return this.state.fileName || this.props.value || "";
}
update({ data, name }) {
this.state.fileName = name || "";
const { fileNameField, record } = this.props;
const changes = { [this.props.name]: data || false };
if (fileNameField in record.fields && record.data[fileNameField] !== name) {
changes[fileNameField] = name || false;
}
return this.props.record.update(changes);
}
async onFileDownload() {
await download({
data: {
model: this.props.record.resModel,
id: this.props.record.resId,
field: this.props.name,
filename_field: this.fileName,
filename: this.fileName || "",
download: true,
data: isBinarySize(this.props.value) ? null : this.props.value,
},
url: "/web/content",
});
}
}
StepViewer.template = "owl_demo.BinaryField3d";
StepViewer.displayName = _lt("3D File");
StepViewer.supportedTypes = ["binary"];
StepViewer.components = {
FileUploader,
};
StepViewer.props = {
...standardFieldProps,
acceptedFileExtensions: { type: String, optional: true },
fileNameField: { type: String, optional: true },
};
StepViewer.defaultProps = {
acceptedFileExtensions: "*.stp",
};
StepViewer.extractProps = ({ attrs }) => {
return {
acceptedFileExtensions: attrs.options.accepted_file_extensions,
fileNameField: attrs.filename,
};
};
registry.category("fields").add("Viewer3D", StepViewer);

View File

@@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.BinaryField3d" owl="1">
<t t-if="!props.readonly">
<t t-if="props.value">
<div class="w-100 d-inline-flex">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
file="{ data: props.value, name: fileName }"
onUploaded.bind="update"
>
<t if="props.record.resId">
<button
class="btn btn-secondary fa fa-download"
data-tooltip="Download"
aria-label="Download"
t-on-click="onFileDownload"
/>
</t>
<t t-set-slot="toggler">
<input type="text" class="o_input" t-att-value="fileName" readonly="readonly" />
<button
class="btn btn-secondary fa fa-pencil o_select_file_button"
data-tooltip="Edit"
aria-label="Edit"
/>
</t>
<button
class="btn btn-secondary fa fa-trash o_clear_file_button"
data-tooltip="Clear"
aria-label="Clear"
t-on-click="() => this.update({})"
/>
</FileUploader>
</div>
</t>
<t t-else="">
<label class="o_select_file_button btn btn-primary">
<FileUploader
acceptedFileExtensions="props.acceptedFileExtensions"
onUploaded.bind="update"
>
<t t-set-slot="toggler">
Upload your file
</t>
</FileUploader>
</label>
</t>
</t>
<t t-elif="props.record.resId and props.value">
<!-- <div t-ref="showStepViewer"/>-->
<model-viewer urc="/owl_demo/static/src/js/3d_viewer/test.glb" ar-modes="scene-viewer webxr quick-look" poster="poster.webp" shadow-intensity="1" camera-orbit="180deg 72.29deg 12640m" field-of-view="30deg">
<div class="progress-bar hide" slot="progress-bar">
<div class="update-bar"></div>
</div>
</model-viewer>
</t>
</t>
<t t-name="owl_demo.StepViewer">
<model-viewer
t-att-src='url'
name="3D model"
alt="3D model"
auto-rotate="1"
camera-controls="1"
>
<div class="text-center mt-4 mb-4 mr-4">
<span
id="model-viewer-fullscreen"
title="View fullscreen"
role="img"
aria-label="Fullscreen"
>
<i class="fa fa-arrows-alt fa-2x" />
</span>
</div>
<!-- <span style="position: absolute;top: 85%;left: 0%;font-size: 9px;" t-if="widget.model == 'jikimo.process.order.line'">-->
<!-- L<t t-esc="widget.recordData.model_length"/>-->
<!-- W:<t t-esc="widget.recordData.model_width"/>-->
<!-- H:<t t-esc="widget.recordData.model_height"/>-->
<!-- V:<t t-esc="widget.recordData.model_volume"/>-->
<!-- </span>-->
</model-viewer>
</t>
</templates>

View File

@@ -1,277 +0,0 @@
import os
def stl_to_gltf(binary_stl_path, out_path, is_binary):
import struct
gltf2 = '''
{
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"buffers" : [
{
%s
"byteLength" : %d
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : %d,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : %d,
"byteLength" : %d,
"target" : 34962
}
],
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5125,
"count" : %d,
"type" : "SCALAR",
"max" : [ %d ],
"min" : [ 0 ]
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : %d,
"type" : "VEC3",
"min" : [%f, %f, %f],
"max" : [%f, %f, %f]
}
],
"asset" : {
"version" : "2.0"
}
}
'''
header_bytes = 80
unsigned_long_int_bytes = 4
float_bytes = 4
vec3_bytes = 4 * 3
spacer_bytes = 2
num_vertices_in_face = 3
vertices = {}
indices = []
if not is_binary:
out_bin = os.path.join(out_path, "out.bin")
out_gltf = os.path.join(out_path, "out.gltf")
else:
out_bin = out_path
unpack_face = struct.Struct("<12fH").unpack
face_bytes = float_bytes*12 + 2
with open(path_to_stl, "rb") as f:
f.seek(header_bytes) # skip 80 bytes headers
num_faces_bytes = f.read(unsigned_long_int_bytes)
number_faces = struct.unpack("<I", num_faces_bytes)[0]
# the vec3_bytes is for normal
stl_assume_bytes = header_bytes + unsigned_long_int_bytes + number_faces * (vec3_bytes*3 + spacer_bytes + vec3_bytes)
assert stl_assume_bytes == os.path.getsize(path_to_stl), "stl is not binary or ill formatted"
minx, maxx = [9999999, -9999999]
miny, maxy = [9999999, -9999999]
minz, maxz = [9999999, -9999999]
vertices_length_counter = 0
data = struct.unpack("<" + "12fH"*number_faces, f.read())
len_data = len(data)
for i in range(0, len_data, 13):
for j in range(3, 12, 3):
x, y, z = data[i+j:i+j+3]
x = int(x*100000)/100000
y = int(y*100000)/100000
z = int(z*100000)/100000
tuple_xyz = (x, y, z);
try:
indices.append(vertices[tuple_xyz])
except KeyError:
vertices[tuple_xyz] = vertices_length_counter
vertices_length_counter += 1
indices.append(vertices[tuple_xyz])
if x < minx: minx = x
if x > maxx: maxx = x
if y < miny: miny = y
if y > maxy: maxy = y
if z < minz: minz = z
if z > maxz: maxz = z
# f.seek(spacer_bytes, 1) # skip the spacer
number_vertices = len(vertices)
vertices_bytelength = number_vertices * vec3_bytes # each vec3 has 3 floats, each float is 4 bytes
unpadded_indices_bytelength = number_vertices * unsigned_long_int_bytes
out_number_vertices = len(vertices)
out_number_indices = len(indices)
unpadded_indices_bytelength = out_number_indices * unsigned_long_int_bytes
indices_bytelength = (unpadded_indices_bytelength + 3) & ~3
out_bin_bytelength = vertices_bytelength + indices_bytelength
if is_binary:
out_bin_uir = ""
else:
out_bin_uir = '"uri": "out.bin",'
gltf2 = gltf2 % ( out_bin_uir,
#buffer
out_bin_bytelength,
# bufferViews[0]
indices_bytelength,
# bufferViews[1]
indices_bytelength,
vertices_bytelength,
# accessors[0]
out_number_indices,
out_number_vertices - 1,
# accessors[1]
out_number_vertices,
minx, miny, minz,
maxx, maxy, maxz
)
glb_out = bytearray()
if is_binary:
gltf2 = gltf2.replace(" ", "")
gltf2 = gltf2.replace("\n", "")
scene = bytearray(gltf2.encode())
scene_len = len(scene)
padded_scene_len = (scene_len + 3) & ~3
body_offset = padded_scene_len + 12 + 8
file_len = body_offset + out_bin_bytelength + 8
# 12-byte header
glb_out.extend(struct.pack('<I', 0x46546C67)) # magic number for glTF
glb_out.extend(struct.pack('<I', 2))
glb_out.extend(struct.pack('<I', file_len))
# chunk 0
glb_out.extend(struct.pack('<I', padded_scene_len))
glb_out.extend(struct.pack('<I', 0x4E4F534A)) # magic number for JSON
glb_out.extend(scene)
while len(glb_out) < body_offset:
glb_out.extend(b' ')
# chunk 1
glb_out.extend(struct.pack('<I', out_bin_bytelength))
glb_out.extend(struct.pack('<I', 0x004E4942)) # magin number for BIN
# print('<%dI' % len(indices))
# print(struct.pack('<%dI' % len(indices), *indices))
glb_out.extend(struct.pack('<%dI' % len(indices), *indices))
for i in range(indices_bytelength - unpadded_indices_bytelength):
glb_out.extend(b' ')
vertices = dict((v, k) for k,v in vertices.items())
# glb_out.extend(struct.pack('f',
# print([each_v for vertices[v_counter] for v_counter in range(number_vertices)]) # magin number for BIN
vertices = [vertices[i] for i in range(number_vertices)]
flatten = lambda l: [item for sublist in l for item in sublist]
# for v_counter in :
# v_3f = vertices[v_counter]
# all_floats_in_vertices.append(v_3f[0])
# all_floats_in_vertices.append(v_3f[1])
# all_floats_in_vertices.append(v_3f[2])
# for v_counter in range(number_vertices):
glb_out.extend(struct.pack('%df' % number_vertices*3, *flatten(vertices))) # magin number for BIN
# for v_counter in range(number_vertices):
# glb_out.extend(struct.pack('3f', *vertices[v_counter])) # magin number for BIN
# for (v_x, v_y, v_z), _ in sorted(vertices.items(), key=lambda x: x[1]):
# glb_out.extend(struct.pack('3f', v_x, v_y, v_z)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_y)) # magin number for BIN
# # glb_out.extend(struct.pack('f', v_z)) # magin number for BIN
with open(out_bin, "wb") as out:
out.write(glb_out)
if not is_binary:
with open(out_gltf, "w") as out:
out.write(gltf2)
print("Done! Exported to %s" %out_path)
if __name__ == '__main__':
import sys
if len(sys.argv) < 3:
print("use it like python3 stl_to_gltf.py /path/to/stl /path/to/gltf/folder")
print("or python3 stl_to_gltf.py /path/to/stl /path/to/glb/file -b")
sys.exit(1)
path_to_stl = sys.argv[1]
out_path = sys.argv[2]
if len(sys.argv) > 3:
is_binary = True
else:
is_binary = False
if out_path.lower().endswith(".glb"):
print("Use binary mode since output file has glb extension")
is_binary = True
else:
if is_binary:
print("output file should have glb extension but not %s", out_path)
if not os.path.exists(path_to_stl):
print("stl file does not exists %s" % path_to_stl)
if not is_binary:
if not os.path.isdir(out_path):
os.mkdir(out_path)
stl_to_gltf(path_to_stl, out_path, is_binary)

View File

@@ -1,25 +0,0 @@
/** @odoo-module **/
const { Component, useState, mount, whenReady, xml } = owl;
export class MyComponent extends Component {
static template = 'owl_demo.my_component'
setup() {
this.state = useState({ is_show:true});
}
onRemove(ev) {
this.state.is_show = false;
}
}
//MyComponent.template = 'owl_demo.MyComponent';
whenReady(() => {
var my_component = new MyComponent();
mount(my_component, document.body);
//$("#myModal").modal('show');
});

View File

@@ -1,84 +0,0 @@
/** @odoo-module **/
const { Component, useState, mount, xml } = owl;
import { registry } from "@web/core/registry";
import { formView } from "@web/views/form/form_view";
import { FormCompiler } from "@web/views/form/form_compiler";
import { FormRenderer } from '@web/views/form/form_renderer';
class PartnerOrderSummary extends Component {
partner = useState({});
constructor(self, partner) {
super();
this.partner = partner;
}
};
PartnerOrderSummary.template = "owl_demo.PartnerOrderSummary";
function compilePartner(el, params) {
// this._rpc({
// model: "res.partner",
// method: "read",
// args: [[this.state.data.partner_id.res_id]]
// }).then(data => {
// (new ComponentWrapper(this,
// PartnerOrderSummary,
// useState(data[0])
// )).mount(element);
// });
return "<h1>hello world</h1>";
}
function compileForm() {
const res = this.compileForm(...arguments);
res.classList.append("test111");
return res;
}
export class SaleOrderFormCompiler extends FormCompiler {
setup() {
super.setup();
this.compilers.unshift(
{ selector: "form", fn: compileForm },
{ selector: "div .o_partner_order_summary", fn: compilePartner }
);
}
}
export class SaleOrderFormRenderer extends FormRenderer { }
SaleOrderFormRenderer.components = {
...FormRenderer.components,
PartnerOrderSummary,
};
registry.category('views').add('sale.view_order_form', {
...formView,
Compiler: SaleOrderFormCompiler,
Renderer: SaleOrderFormRenderer,
});
/*** 重载表单渲染器对任何包含o_partner_order_summary这一class 的div挂载当前组件 */
//FormRenderer.include({
// _render: async function() {
// await this._super(...arguments);
// for (const element of this.el.querySelectorAll(".o_partner_order_summary")) {
// this._rpc({
// model: "res.partner",
// method: "read",
// args: [[this.state.data.partner_id.res_id]]
// }).then(data => {
// (new ComponentWrapper(this,
// PartnerOrderSummary,
// useState(data[0])
// )).mount(element);
// });
// }
// }
//});

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.field_team_id">
<ul class="list-group">
<li class="list-group-item active">Cras justo odio</li>
<li class="list-group-item">Dapibus ac facilisis in</li>
<li class="list-group-item">Morbi leo risus</li>
<li class="list-group-item">Porta ac consectetur ac</li>
<li class="list-group-item">Vestibulum at eros</li>
</ul>
</t>
<!-- 不要加 owl="1" -->
<t t-name="owl_demo.field_order_line">
<!-- <t t-foreach="current_field" t-as="order_line">-->
<!-- <h6><t t-out="order_line.name"/>-->
<!-- <span class="badge rounded-pill dropdown o_tag o_tag_color_7"><t t-out="order_line.product_uom_qty"/></span></h6>-->
<!-- -->
<!-- </t>-->
<table class="table table-bordered">
<!-- <thead>-->
<!-- <tr>-->
<!-- <th scope="col">#</th>-->
<!-- <th scope="col">product</th>-->
<!-- <th scope="col">qty</th>-->
<!-- <th scope="col">subtotal</th>-->
<!-- </tr>-->
<!-- </thead>-->
<tbody>
<t t-set="index" t-value="1"/>
<t t-foreach="current_field" t-as="order_line">
<tr class="o_tag o_tag_color_{{index}}">
<th scope="row"><t t-out="index"/></th>
<td><t t-out="order_line.name.substr(0,20)"/></td>
<td><t t-out="order_line.product_uom_qty"/></td>
<td><t t-out="order_line.price_subtotal"/></td>
</tr>
<t t-set="index" t-value="index+1"/>
</t>
</tbody>
</table>
</t>
<t t-name="owl_demo.field_partner_id">
<h5><t t-out="current_field.name"/></h5>
<span class="badge rounded-pill dropdown o_tag o_tag_color_3"><t t-out="current_field.phone"/></span>
</t>
<!-- <t t-name="owl_demo.field_order_line">-->
<!--&lt;!&ndash; <h3><t t-out="record.data.name"/></h3>&ndash;&gt;-->
<!-- <ul class="list-group">-->
<!-- <t t-foreach="record.data.order_line.records" t-as="order_line">-->
<!-- <li class="list-group-item d-flex justify-content-between align-items-center">-->
<!-- <t t-out="order_line.id"/>-->
<!-- <span class="badge rounded-pill">14</span>-->
<!-- </li>-->
<!-- </t>-->
<!-- </ul>-->
<!-- </t>-->
</templates>

View File

@@ -1,97 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
import { session } from "@web/session";
import core from 'web.core';
import rpc from 'web.rpc';
var QWeb = core.qweb;
import { Component,useRef,useEffect } from "@odoo/owl";
export class CTemplateField extends Component {
setup() {
this.show_template = useRef('showTemplate')
useInputField({
getValue: () => this.props.value || ""
});
useEffect(
(el) => {
if (!el) {
return;
}
return this.formatTemplate($(el));
},
() => [this.show_template.el],
)
}
formatTemplate($el) {
console.log($el)
var self=this;
if (this.props.readonly && this.props.value) {
//value = this.props.value + ' | ' + this.props.template_xml_id;
if (this.props.type == 'one2many' || this.props.type == 'many2many'){
// 如果是one2many或many2many就重新获取对象列表
rpc.query({
model: this.props.value.resModel,
method: 'read',
args: [this.props.value.currentIds,[]],
}).then((result) => {
console.log(result);
return $el.html(QWeb.render(this.props.template_xml_id,{'record':this.props.record,'current_field':result}))
});
}
else if (this.props.type == 'many2one'){
// 如果是one2many或many2many就重新获取对象列表
rpc.query({
model: this.props.record.fields[this.props.name].relation,
method: 'read',
args: [this.props.value[0],[]],
}).then((result) => {
console.log(result);
return $el.html(QWeb.render(this.props.template_xml_id,{'record':this.props.record,'current_field':result[0]}))
});
}
else{
if (this.props.template_xml_id === undefined)
{
return $el.html(this.props.value);
}else{
return $el.html(QWeb.render(this.props.template_xml_id,{'record':this.props.record,'current_field':this.props.value}));
}
}
}
//
}
}
CTemplateField.template = "owl_demo.CTemplateField";
CTemplateField.props = {
...standardFieldProps,
template_xml_id: { type: String, optional: true },
placeholder: { type: String, optional: true },
};
CTemplateField.defaultProps = {
// hideSymbol: false,
// inputType: "text",
};
CTemplateField.supportedTypes = ["many2one","char"];
CTemplateField.displayName = _lt("CTemplate");
CTemplateField.extractProps = ({ attrs }) => {
return {
template_xml_id: attrs.options.template,
placeholder: attrs.placeholder,
};
};
registry.category("fields").add("CTemplate", CTemplateField);

View File

@@ -1,3 +0,0 @@
.o_field_widget.o_field_template {
display: block;
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.CTemplateField" owl="1">
<div t-ref="showTemplate"/>
</t>
</templates>

View File

@@ -1,25 +0,0 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
const serviceRegistry = registry.category("services");
const myService = {
dependencies: ["notification","title","effect"],
start(env, { notification, title,effect }) {
// let counter = 1;
// setInterval(() => {
// var tik_str = `Tick Tock ${counter++}`;
// notification.add(tik_str);
// title.setParts({ odoo: tik_str, fruit: tik_str });
// effect.add({
// type: "rainbow_man", // can be omitted, default type is already "rainbow_man"
// message: "Boom! Team record for the past 30 days." + tik_str,
// });
// }, 5000);
}
};
serviceRegistry.add("myService", myService);

View File

@@ -1,50 +0,0 @@
/** @odoo-module **/
import AbstractField from 'web.AbstractField';
import fieldRegistry from 'web.field_registry';
export const ShowUnitsWidgetField = AbstractField.extend({
supportedFieldTypes: ['float','char','datetime'],
/**
* @override
*/
// init: function () {
// this._super.apply(this, arguments);
// this.units = this.nodeOptions && this.nodeOptions.units || '';
// },
/**
* @override
*/
_renderReadonly() {
this.units = this.nodeOptions && this.nodeOptions.units || '';
this.$el.empty().html(this._formatValue(this.value) + " <b>"+this.units+"</b>");
},
});
fieldRegistry.add('show_units', ShowUnitsWidgetField);
export const ShowUnitsWidgetField = AbstractField.extend({
supportedFieldTypes: ['float','char','datetime'],
/**
* @override
*/
// init: function () {
// this._super.apply(this, arguments);
// this.units = this.nodeOptions && this.nodeOptions.units || '';
// },
/**
* @override
*/
_renderReadonly() {
this.units = this.nodeOptions && this.nodeOptions.units || '';
this.$el.empty().html(this._formatValue(this.value) + " <b>"+this.units+"</b>");
},
});
fieldRegistry.add('show_units', ShowUnitsWidgetField);

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="owl_demo.my_component">
<div t-on-click="increment">
<t t-esc="state.value"/>
</div>
</t>
</templates>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
</templates>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="owl_demo.PartnerOrderSummary" owl="1">
<div class="center"
style="width: 100%; text-align: center; border: 1px solid #cecece; padding: 2rem 20%; margin: 12px 0;">
<img
t-attf-src="data:image/jpg;base64,{{partner.image_256}}"
width="75px"
height="75px"
style="background-color: #ccc; border-radius: 50%; margin-bottom: 10px;"/>
<!-- Customer name -->
<p style="font-size: 16px; color: #4d4b4b;"><strong t-esc="partner.name"/></p>
<!-- Address -->
<p style="font-size: 12px; color: #8c8787;">
<i class="fa fa-map-marker" style="padding-right: 4px;"/>
<span t-esc="partner.city"/>
<span t-esc="partner.zip" style="margin-left: 5px;"/>
</p>
<!-- Grid of previous order stats -->
<div class="row" style="padding-top: 20px;">
<div class="col-6" style="border-right: 1px solid #ccc;">
<p style="font-size: 20px;">
<strong t-esc="partner.sale_order_count"/>
</p>
<p style="font-size: 12px; color: #8c8787;">Orders</p>
</div>
<div class="col-6">
<p style="font-size: 20px;">
<strong t-esc="partner.total_invoiced" t-options='{"widget": "monetary"}'/>
</p>
<p style="font-size: 12px; color: #8c8787;">Total Sales</p>
</div>
</div>
</div>
</t>
</templates>

43
requirements.txt Normal file
View File

@@ -0,0 +1,43 @@
Babel==2.9.1 # min version = 2.6.0 (Focal with security backports)
chardet==3.0.4
cryptography==3.4.8
decorator==4.4.2
docutils==0.16
ebaysdk==2.1.5
freezegun==0.3.15; python_version >= '3.8'
gevent==1.5.0 ; python_version == '3.7'
gevent==21.8.0 ; python_version > '3.9' # (Jammy)
greenlet==1.1.2 ; python_version > '3.9' # (Jammy)
idna==2.8
Jinja2==2.11.3 # min version = 2.10.1 (Focal - with security backports)
libsass==0.18.0
lxml==4.6.5 # min version = 4.5.0 (Focal - with security backports)
MarkupSafe==1.1.0
num2words==0.5.6
ofxparse==0.21; python_version > '3.9' # (Jammy)
passlib==1.7.3 # min version = 1.7.2 (Focal with security backports)
Pillow==9.0.1 # min version = 7.0.0 (Focal with security backports)
polib==1.1.0
psutil==5.6.7 # min version = 5.5.1 (Focal with security backports)
psycopg2==2.8.6; sys_platform == 'win32' or python_version >= '3.8'
pydot==1.4.1
pyopenssl==19.0.0
PyPDF2==1.26.0
pypiwin32 ; sys_platform == 'win32'
pyserial==3.4
python-dateutil==2.7.3
python-ldap==3.4.0 ; sys_platform != 'win32' # min version = 3.2.0 (Focal with security backports)
python-stdnum==1.13
pytz==2019.3
pyusb==1.0.2
qrcode==6.1
reportlab==3.5.59 # version < 3.5.54 are not compatible with Pillow 8.1.2 and 3.5.59 is bullseye
requests==2.25.1 # versions < 2.25 aren't compatible w/ urllib3 1.26. Bullseye = 2.25.1. min version = 2.22.0 (Focal)
urllib3==1.26.5 # indirect / min version = 1.25.8 (Focal with security backports)
vobject==0.9.6.1
Werkzeug==0.16.1 ; python_version <= '3.9'
Werkzeug==2.0.2 ; python_version > '3.9' # (Jammy)
xlrd==1.2.0; python_version >= '3.8'
XlsxWriter==1.1.2
xlwt==1.3.*
zeep==3.4.0

View File

@@ -49,6 +49,7 @@ class MrsProductionProcess(models.Model):
processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序') processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序')
partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂') partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂')
active = fields.Boolean('有效', default=True) active = fields.Boolean('有效', default=True)
parameter_ids = fields.One2many('sf.production.process.parameter', 'process_id', string='可选参数')
class MrsProcessingTechnology(models.Model): class MrsProcessingTechnology(models.Model):
@@ -96,3 +97,12 @@ class SupplierSort(models.Model):
('supplier_sort_uniq', 'unique (partner_id,materials_model_id)', '排序不能重复!') ('supplier_sort_uniq', 'unique (partner_id,materials_model_id)', '排序不能重复!')
] ]
class MrsProductionProcessParameter(models.Model):
_name = 'sf.production.process.parameter'
_description = '可选参数'
name = fields.Char('参数名')
active = fields.Boolean('有效', default=True)
price = fields.Float('单价')
process_id = fields.Many2one('sf.production.process', string='表面工艺')
materials_model_ids = fields.Many2many('sf.materials.model', 'applicable_material', string='适用材料')
code = fields.Char("编码")

View File

@@ -14,7 +14,7 @@ access_sf_processing_technology,sf_processing_technology,model_sf_processing_tec
access_sf_tray,sf_tray,model_sf_tray,base.group_user,1,1,1,1 access_sf_tray,sf_tray,model_sf_tray,base.group_user,1,1,1,1
access_sf_supplier_sort,sf_supplier_sort,model_sf_supplier_sort,base.group_user,1,1,1,1 access_sf_supplier_sort,sf_supplier_sort,model_sf_supplier_sort,base.group_user,1,1,1,1
access_sf_production_process_parameter,sf_production_process_parameter,model_sf_production_process_parameter,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
14 access_sf_tray sf_tray model_sf_tray base.group_user 1 1 1 1
15 access_sf_supplier_sort sf_supplier_sort model_sf_supplier_sort base.group_user 1 1 1 1
16 access_sf_production_process_parameter sf_production_process_parameter model_sf_production_process_parameter base.group_user 1 1 1 1
17
18
19
20

View File

@@ -74,6 +74,32 @@
</form> </form>
</field> </field>
</page> </page>
<page string="可选参数">
<field name="parameter_ids">
<tree force_save="1">
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="price"/>
<field name='process_id' default="default" invisible="1"/>
</tree>
<form>
<sheet>
<group>
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="price"/>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</page>
</notebook> </notebook>
<group> <group>
<field name="remark"/> <field name="remark"/>

View File

@@ -131,4 +131,15 @@
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
<field name="doall" eval="False"/> <field name="doall" eval="False"/>
</record> </record>
<record model="ir.cron" id="sf_cron13">
<field name="name">同步表面工艺可选参数</field>
<field name="model_id" ref="model_sf_production_process_parameter"/>
<field name="state">code</field>
<field name="code">model.sync_production_process_parameter()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
</odoo> </odoo>

View File

@@ -2,7 +2,7 @@
# Part of SmartGo. See LICENSE file for full copyright and licensing details. # Part of SmartGo. See LICENSE file for full copyright and licensing details.
import logging import logging
from odoo import api, fields, models from odoo import api, fields, models
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -19,29 +19,33 @@ class ResConfigSettings(models.TransientModel):
ftp_password = fields.Char(string='FTP密码') ftp_password = fields.Char(string='FTP密码')
def sf_all_sync(self): def sf_all_sync(self):
self.env['sf.production.materials'].sync_all_production_materials() try:
_logger.info("同步资源库材料") self.env['sf.production.materials'].sync_all_production_materials()
self.env['sf.materials.model'].sync_all_materials_model() _logger.info("同步资源库材料")
_logger.info("同步资源库材料型号") self.env['sf.materials.model'].sync_all_materials_model()
self.env['sf.production.process'].sync_all_production_process() _logger.info("同步资源库材料型号")
_logger.info("同步资源库表面工艺") self.env['sf.production.process'].sync_all_production_process()
self.env['sf.processing.technology'].sync_all_processing_technology() _logger.info("同步资源库表面工艺")
_logger.info("同步资源库加工工艺") self.env['sf.processing.technology'].sync_all_processing_technology()
self.env['sf.machine.brand.tags'].sync_all_machine_brand_tags() _logger.info("同步资源库加工工艺")
_logger.info("同步资源库品牌类别") self.env['sf.machine.brand.tags'].sync_all_machine_brand_tags()
self.env['sf.machine.brand'].sync_all_machine_brand() _logger.info("同步资源库品牌类别")
_logger.info("同步资源库品牌") self.env['sf.machine.brand'].sync_all_machine_brand()
self.env['sf.machine.control_system'].sync_all_machine_tool_type_control_system() _logger.info("同步资源库品牌")
_logger.info("同步资源库控制系统") self.env['sf.machine.control_system'].sync_all_machine_tool_type_control_system()
# self.env['sf.machine_tool'].sync_all_machine_tool() _logger.info("同步资源库控制系统")
# _logger.info("同步机床行业编码") self.env['sf.machine_tool.type'].sync_all_machine_tool_type()
self.env['sf.machine_tool.type'].sync_all_machine_tool_type() _logger.info("同步资源库机床型号")
_logger.info("同步资源库机床型号") self.env['sf.cutting_tool.category'].sync_all_cutting_tool_category()
self.env['sf.cutting_tool.category'].sync_all_cutting_tool_category() _logger.info("同步资源库刀具类别")
_logger.info("同步资源库刀具类别") self.env['sf.cutting_tool.type'].sync_all_cutting_tool_type()
self.env['sf.cutting_tool.type'].sync_all_cutting_tool_type() _logger.info("同步资源库刀具")
_logger.info("同步资源库刀具") self.env['sf.production.process.parameter'].sync_all_production_process_parameter()
# self.env['sf.processing.order'].sync_all_processing_order() _logger.info("同步表面工艺参数")
except Exception as e:
_logger.info("捕获错误信息:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@api.model @api.model
def get_values(self): def get_values(self):

View File

@@ -948,3 +948,75 @@ class sfProcessingOrder(models.Model):
}) })
else: else:
raise ValidationError("认证未通过") raise ValidationError("认证未通过")
class sfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter'
_description = '表面工艺可选参数'
url = '/api/production_process_parameter/list'
# 定时同步每日表面工艺
def sync_production_process_parameter(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
headers = Common.get_headers(self, token, sf_secret_key)
strUrl = sf_sync_config['sf_url'] + self.url
r = requests.post(strUrl, json={}, data=None, headers=headers)
r = r.json()
result = json.loads(r['result'])
if result['status'] == 1:
for item in result['mrs_production_process_parameter_yesterday_list']:
if item:
brand = self.env['sf.production.process.parameter'].search(
[("code", '=', item['code'])])
if brand:
brand.name = item['name'],
brand.code = item['code'],
brand.active = item['active'],
brand.price = item['price'],
else:
self.env['sf.production.process.parameter'].create({
"name": item['name'],
"code": item['code'],
"active": item['active'],
"price" : item['price'],
"process_id": self.env['sf.production.process'].search(
[('process_encode', '=', item['process_id_code'])]).id,
'materials_model_ids': self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])])
})
else:
raise ValidationError("认证未通过") # 定时同步表面工艺
# 同步所有表面工艺
def sync_all_production_process_parameter(self):
sf_sync_config = self.env['res.config.settings'].get_values()
token = sf_sync_config['token']
sf_secret_key = sf_sync_config['sf_secret_key']
headers = Common.get_headers(self, token, sf_secret_key)
strUrl = sf_sync_config['sf_url'] + self.url
r = requests.post(strUrl, json={}, data=None, headers=headers)
r = r.json()
result = json.loads(r['result'])
if result['status'] == 1:
for item in result['mrs_production_process_parameter_all_list']:
if item:
brand = self.env['sf.production.process.parameter'].search(
[("code", '=', item['code'])])
if not brand:
self.env['sf.production.process.parameter'].create({
"name": item['name'],
"code": item['code'],
"price": item['price'],
"active": item['active'],
"process_id": self.env['sf.production.process'].search(
[('process_encode', '=', item['process_id_code'])]).id,
'materials_model_ids': self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])])
})
else:
raise ValidationError("认证未通过")