Compare commits
510 Commits
release/re
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1a08be57b | ||
|
|
a3356fe195 | ||
|
|
3c70e1623e | ||
|
|
51628c081a | ||
|
|
18ebe6bcb4 | ||
|
|
63ac4f2b44 | ||
|
|
2966260f51 | ||
|
|
c4069995e0 | ||
|
|
54920982a2 | ||
|
|
f947a3ed58 | ||
|
|
5e86d67316 | ||
|
|
7354a19696 | ||
|
|
78e3d77000 | ||
|
|
47ad2410cf | ||
|
|
fa2eb7b3cf | ||
|
|
0cee6ebd77 | ||
|
|
73f161ffa4 | ||
|
|
ff06d5c3be | ||
|
|
78cb177ac5 | ||
|
|
9601502360 | ||
|
|
739b7e600c | ||
|
|
27da7639b2 | ||
|
|
258e3bdb9f | ||
|
|
7f3e9927d5 | ||
|
|
927f726030 | ||
|
|
7576200fc4 | ||
|
|
4ffb9d636f | ||
|
|
24f0547343 | ||
|
|
402a323673 | ||
|
|
5e1685417f | ||
|
|
9dfe34ce9a | ||
|
|
190d6da217 | ||
|
|
8946b44280 | ||
|
|
f21d085462 | ||
|
|
43f53197c4 | ||
|
|
ac43be1262 | ||
|
|
8bf1c56efd | ||
|
|
d66791dc41 | ||
|
|
ce6b36a77e | ||
|
|
4acae01009 | ||
|
|
213b76a280 | ||
|
|
c675e1d67c | ||
|
|
8a9f6ab34f | ||
|
|
8aca6ce084 | ||
|
|
cb0c093006 | ||
|
|
1e7d7008b4 | ||
|
|
ef453dbc1e | ||
|
|
9f1e635c8d | ||
|
|
92037f3f04 | ||
|
|
4ba0566d27 | ||
|
|
5d870d53f6 | ||
|
|
6a674cdb5b | ||
|
|
fbce132173 | ||
|
|
98e13cba99 | ||
|
|
7ce2f6c797 | ||
|
|
7502792438 | ||
|
|
9efb13cf01 | ||
|
|
b14e263a2e | ||
|
|
6f41ef3047 | ||
|
|
de29f0c938 | ||
|
|
589a24f595 | ||
|
|
85186f94f5 | ||
|
|
ffc363a31c | ||
|
|
a047ad3bb6 | ||
|
|
cc3289c834 | ||
|
|
e1093a3c69 | ||
|
|
ba955ca658 | ||
|
|
946abc5cf7 | ||
|
|
57ea9ed8f4 | ||
|
|
f8a12b1fe3 | ||
|
|
722f6257ae | ||
|
|
ccbe311c58 | ||
|
|
0f6ca0876c | ||
|
|
bb60fd0339 | ||
|
|
eefbd5939b | ||
|
|
7c01196f17 | ||
|
|
4c58f4d7f3 | ||
|
|
c741587341 | ||
|
|
6ad2fa80f1 | ||
|
|
55694ae303 | ||
|
|
8423b12119 | ||
|
|
5a7ba7f87e | ||
|
|
d36da3df24 | ||
|
|
302a5f0cf1 | ||
|
|
b4fef421dd | ||
|
|
5f5e991d33 | ||
|
|
43a49242c5 | ||
|
|
2e5694cd1a | ||
|
|
3c123a07ea | ||
|
|
dec5ddd154 | ||
|
|
22864b9669 | ||
|
|
528fa483a2 | ||
|
|
b5a3815f1f | ||
|
|
1719e0394e | ||
|
|
eae902ee92 | ||
|
|
01c57a8691 | ||
|
|
2808005ce9 | ||
|
|
06c5f65df2 | ||
|
|
e0bad6ed40 | ||
|
|
35c8b9316e | ||
|
|
2ddfbb0dde | ||
|
|
988c4a460d | ||
|
|
c050e2f11a | ||
|
|
95e49c7b0f | ||
|
|
e46a13889e | ||
|
|
335151f6ca | ||
|
|
908e4a3df6 | ||
|
|
6b403f2c1e | ||
|
|
da5e322229 | ||
|
|
4f9ac16a07 | ||
|
|
4342844845 | ||
|
|
8bc68e1edd | ||
|
|
763d64a1ca | ||
|
|
8a10d18f37 | ||
|
|
181f490d47 | ||
|
|
322718d19a | ||
|
|
96eeb1cab7 | ||
|
|
d5c82e4a28 | ||
|
|
436adc5ff6 | ||
|
|
97a5d91fce | ||
|
|
9eee8557c2 | ||
|
|
4394dd023e | ||
|
|
d39bbe4f99 | ||
|
|
b188c8adfe | ||
|
|
ea6fd42b2e | ||
|
|
625499f758 | ||
|
|
0c2d6dd582 | ||
|
|
f6a9c97e7f | ||
|
|
e2d741937e | ||
|
|
9a3404f4b8 | ||
|
|
d000aa095f | ||
|
|
8945801db8 | ||
|
|
40f327ea17 | ||
|
|
44a2604dbd | ||
|
|
0ae7424f04 | ||
|
|
091af92e8d | ||
|
|
3ed338ed64 | ||
|
|
65781570e7 | ||
|
|
e1f7aeaa96 | ||
|
|
a0a580588c | ||
|
|
a2f2a21a4c | ||
|
|
1ddb446bc6 | ||
|
|
dcdc6851b6 | ||
|
|
ece4e56118 | ||
|
|
7cd104c047 | ||
|
|
80a7dd75c8 | ||
|
|
ec2ef752b3 | ||
|
|
57d5c57048 | ||
|
|
9b7c22c35e | ||
|
|
6711f6f6e6 | ||
|
|
7ac7077f8c | ||
|
|
ed20249988 | ||
|
|
dba8d23348 | ||
|
|
ab9062983c | ||
|
|
37493a4688 | ||
|
|
689d149edc | ||
|
|
9b9106c5da | ||
|
|
9a43af98c3 | ||
|
|
1eae92f2b2 | ||
|
|
4560bbc0ed | ||
|
|
2d5ef0aae4 | ||
|
|
096542c8ff | ||
|
|
d123ca5173 | ||
|
|
388929f207 | ||
|
|
29b9223a14 | ||
|
|
cf283377cd | ||
|
|
5f218e69d4 | ||
|
|
7f5fb165de | ||
|
|
eec79d7d3e | ||
|
|
00809d88f6 | ||
|
|
18b0cc6ea0 | ||
|
|
54d9902ae4 | ||
|
|
eff39a39e3 | ||
|
|
005a7bb68e | ||
|
|
afc1701150 | ||
|
|
72eada0639 | ||
|
|
979e8a47ab | ||
|
|
e119a364c1 | ||
|
|
c7b9bbb18d | ||
|
|
3cb3dce247 | ||
|
|
a7e1c89e66 | ||
|
|
ca1060ada5 | ||
|
|
69578ab054 | ||
|
|
511bed47ff | ||
|
|
26a0412649 | ||
|
|
4989ff773c | ||
|
|
04c0d68763 | ||
|
|
589035d42b | ||
|
|
5726d50106 | ||
|
|
6e9c326a52 | ||
|
|
fc1cdf4d0e | ||
|
|
45178ac38b | ||
|
|
09ee1c26b1 | ||
|
|
64180ca57f | ||
|
|
0c052a118c | ||
|
|
cb9fd43f11 | ||
|
|
1dc55c2770 | ||
|
|
0ca6d46a7b | ||
|
|
3624ef4755 | ||
|
|
f07a003f2c | ||
|
|
0ef46407dd | ||
|
|
f7f3c005f8 | ||
|
|
cae5e14587 | ||
|
|
a2990b8f2e | ||
|
|
5121e455d2 | ||
|
|
4a14fa6bf6 | ||
|
|
f251878637 | ||
|
|
9a5aa52801 | ||
|
|
5b3193d3ff | ||
|
|
52befb6233 | ||
|
|
9ce1963d44 | ||
|
|
1b710b205f | ||
|
|
0d253c54c8 | ||
|
|
d2c5fdb509 | ||
|
|
40209958f2 | ||
|
|
778896a670 | ||
|
|
fcacf609e9 | ||
|
|
5cd134758a | ||
|
|
b8f8e90444 | ||
|
|
b50a852f77 | ||
|
|
ea94d74657 | ||
|
|
b5bea1f811 | ||
|
|
8416630593 | ||
|
|
6572ca25fe | ||
|
|
6a7e6ee5c5 | ||
|
|
aff35f2dd4 | ||
|
|
8ead5655cf | ||
|
|
d6b3a5a3f1 | ||
|
|
19d8f6ae73 | ||
|
|
ae8e304d7f | ||
|
|
e154f5b763 | ||
|
|
804bdb60e8 | ||
|
|
302636635c | ||
|
|
c465822774 | ||
|
|
f9063bf3d9 | ||
|
|
ea6fb5e570 | ||
|
|
0e672aef79 | ||
|
|
11ec04de5b | ||
|
|
efe98b5133 | ||
|
|
2d2abfce25 | ||
|
|
821a7a63be | ||
|
|
de221ba67e | ||
|
|
c5bb9a32d0 | ||
|
|
925fc2f2b3 | ||
|
|
41f379ef5f | ||
|
|
acc791978d | ||
|
|
f24c1ab4a5 | ||
|
|
179899e483 | ||
|
|
b8d21b7fa5 | ||
|
|
295f7ecae3 | ||
|
|
d2a76a03d2 | ||
|
|
1e172cb4e3 | ||
|
|
4424cb58f4 | ||
|
|
cd0ea08b21 | ||
|
|
10065e95d3 | ||
|
|
2c06c6f6a1 | ||
|
|
1eeaf2f85d | ||
|
|
aa96e63fff | ||
|
|
7ac09653c3 | ||
|
|
9c3ed0166b | ||
|
|
3c8ce870df | ||
|
|
d8e65a20d5 | ||
|
|
10a622b52d | ||
|
|
c1ffd3f870 | ||
|
|
a98b7456c0 | ||
|
|
7e3d40bd38 | ||
|
|
83d45b6d3f | ||
|
|
fe237dc742 | ||
|
|
35c13dc51c | ||
|
|
08477e4d94 | ||
|
|
069d1f50b0 | ||
|
|
dd60dee22d | ||
|
|
e49362e2e3 | ||
|
|
d5b5231873 | ||
|
|
2b080c1639 | ||
|
|
bd3bfc979e | ||
|
|
5a3233d539 | ||
|
|
3e8f045a40 | ||
|
|
d1fab6aab0 | ||
|
|
a3357e01aa | ||
|
|
c46a148856 | ||
|
|
5794f75f0b | ||
|
|
01d9d4a636 | ||
|
|
2931d6a92d | ||
|
|
5e6ae1ff55 | ||
|
|
d7a0dc578b | ||
|
|
44a11c839f | ||
|
|
9808f49b3d | ||
|
|
b40a87df88 | ||
|
|
72e9443048 | ||
|
|
c6f6927d57 | ||
|
|
ade7588a9c | ||
|
|
281f03670e | ||
|
|
7356e0afb7 | ||
|
|
fcd86e230a | ||
|
|
bf1271a742 | ||
|
|
324ed283c4 | ||
|
|
ebb8007549 | ||
|
|
361f187026 | ||
|
|
b35f444e49 | ||
|
|
9b2a2d644d | ||
|
|
93ca2000a1 | ||
|
|
ca43aa836e | ||
|
|
26ad5b5b3f | ||
|
|
937ea42af3 | ||
|
|
9b957848c2 | ||
|
|
36579d22ac | ||
|
|
e662490cb5 | ||
|
|
038ce8e139 | ||
|
|
e1a4784092 | ||
|
|
6d4d393b9b | ||
|
|
c956b06f57 | ||
|
|
da02d68c12 | ||
|
|
eb9b43dc91 | ||
|
|
ab66e24c9d | ||
|
|
fbbce6332d | ||
|
|
554b86e641 | ||
|
|
54317a529e | ||
|
|
78d00e9157 | ||
|
|
89cb61f244 | ||
|
|
deef246a6d | ||
|
|
9de201705f | ||
|
|
8f8d83d4cb | ||
|
|
c703012ec4 | ||
|
|
f1780181fa | ||
|
|
f5e36f601c | ||
|
|
6c0b84ec43 | ||
|
|
3e438a10ee | ||
|
|
9218633a5e | ||
|
|
c646e70a66 | ||
|
|
40521e06a8 | ||
|
|
7fee98cdee | ||
|
|
4b7c277a25 | ||
|
|
064397b32b | ||
|
|
048155bc9c | ||
|
|
d439c9140d | ||
|
|
296659ebb0 | ||
|
|
a82063d078 | ||
|
|
f17ff2bc57 | ||
|
|
5df3de7dcd | ||
|
|
b37738fbc3 | ||
|
|
79f3dce0ec | ||
|
|
4b87ea3f0e | ||
|
|
098ac0e7e9 | ||
|
|
6825364a2b | ||
|
|
0cac8fbb4f | ||
|
|
dce748c6a7 | ||
|
|
536c766272 | ||
|
|
422edbcb02 | ||
|
|
d13961ff5f | ||
|
|
b603934637 | ||
|
|
62d303168e | ||
|
|
39bc206344 | ||
|
|
715d835633 | ||
|
|
9e356682dc | ||
|
|
5dc392a27b | ||
|
|
3d2c62f5db | ||
|
|
1e3f0d5ee5 | ||
|
|
b26919a40d | ||
|
|
9ffe338532 | ||
|
|
08d333e4e2 | ||
|
|
47c501c6c6 | ||
|
|
335c79e618 | ||
|
|
ba986995ed | ||
|
|
2a45cdd53a | ||
|
|
0cdf37bb50 | ||
|
|
bae2592587 | ||
|
|
c86cc27510 | ||
|
|
2dded43e39 | ||
|
|
eb6e638018 | ||
|
|
f64c21dacd | ||
|
|
426aa78ed4 | ||
|
|
42c65496ff | ||
|
|
e893abd83e | ||
|
|
78e2e7184e | ||
|
|
ce575adc88 | ||
|
|
6668e0ee2f | ||
|
|
647d21dea3 | ||
|
|
0e13e2190e | ||
|
|
7f3d50a130 | ||
|
|
7b2908defa | ||
|
|
a8211171b0 | ||
|
|
c57daa2c52 | ||
|
|
4ed7ecf628 | ||
|
|
8a6ebb331a | ||
|
|
b333e27c51 | ||
|
|
6761851407 | ||
|
|
ada7936d1b | ||
|
|
9c40aa9a7e | ||
|
|
0694541653 | ||
|
|
e4e2dca22c | ||
|
|
e259fd0c05 | ||
|
|
a767c6491f | ||
|
|
6c2045d0c3 | ||
|
|
6fe56605ab | ||
|
|
bf2c7fd3c5 | ||
|
|
54fc91baa2 | ||
|
|
376f2b3079 | ||
|
|
3c50f19e2d | ||
|
|
08622b10b9 | ||
|
|
dc5b68cca0 | ||
|
|
1d399527e0 | ||
|
|
b3bfa69656 | ||
|
|
96e226836e | ||
|
|
062ca66328 | ||
|
|
7ab8269088 | ||
|
|
1241737dea | ||
|
|
813e424ec9 | ||
|
|
8c08893aa1 | ||
|
|
5ad35de76b | ||
|
|
3c8dac799d | ||
|
|
29c1c7a54d | ||
|
|
2c27a9b575 | ||
|
|
0e4b9e44f8 | ||
|
|
a07a1ba268 | ||
|
|
f537fbc597 | ||
|
|
a6b7167f3b | ||
|
|
c4de966dea | ||
|
|
03cfc00be5 | ||
|
|
cc05e8423e | ||
|
|
e78516f73c | ||
|
|
a277bb402e | ||
|
|
e5793638f7 | ||
|
|
9673fd165d | ||
|
|
e92905fe32 | ||
|
|
1b521ae460 | ||
|
|
4e0d8f1c88 | ||
|
|
aecf2121a1 | ||
|
|
e2d7576a5e | ||
|
|
cc13e5cd9a | ||
|
|
271f34a03b | ||
|
|
366e816268 | ||
|
|
1532184008 | ||
|
|
9f180e307d | ||
|
|
f0a4cf1d0f | ||
|
|
6c734eead4 | ||
|
|
f7e4ce416a | ||
|
|
5a60eed5b1 | ||
|
|
bf34de58fc | ||
|
|
cccc2f8493 | ||
|
|
894d3b9ea3 | ||
|
|
0b85f29262 | ||
|
|
9dbea66b73 | ||
|
|
9a7ac4dfa6 | ||
|
|
e6ca4c27ac | ||
|
|
a8560a0684 | ||
|
|
4f74996f24 | ||
|
|
5b26abc203 | ||
|
|
49654f1ff6 | ||
|
|
a9b7f99944 | ||
|
|
34e858ffe4 | ||
|
|
41efe81119 | ||
|
|
7cab8cd287 | ||
|
|
5f9c5961a5 | ||
|
|
a79500d0ad | ||
|
|
4399700c3d | ||
|
|
36fd17b6c7 | ||
|
|
9d52466f61 | ||
|
|
112efccb7c | ||
|
|
45b9177da4 | ||
|
|
745658eff2 | ||
|
|
ce8b0127b8 | ||
|
|
f58fb2ea13 | ||
|
|
d333621c7a | ||
|
|
563023fa3c | ||
|
|
3d2c7425c4 | ||
|
|
aaa19b96fd | ||
|
|
5d8f0f83b2 | ||
|
|
1547a6064f | ||
|
|
55b2daa0a3 | ||
|
|
d469c09f15 | ||
|
|
a03cb31b29 | ||
|
|
d2b20761b5 | ||
|
|
0f47b4b8c3 | ||
|
|
0cfb16b9d9 | ||
|
|
8ec5e05739 | ||
|
|
5393ef686a | ||
|
|
e92c9675b6 | ||
|
|
e6bad80031 | ||
|
|
070890e3b6 | ||
|
|
6e50774b23 | ||
|
|
fc18d34495 | ||
|
|
ed0d57c364 | ||
|
|
3f27ae0f35 | ||
|
|
cec426920b | ||
|
|
09a6255d9c | ||
|
|
d187bb2bba | ||
|
|
a5da18bbc7 | ||
|
|
510e8d49fb | ||
|
|
051273017d | ||
|
|
f49557090a | ||
|
|
115a3e0712 | ||
|
|
d49e8779f9 | ||
|
|
81dbbf980c | ||
|
|
39214e5352 | ||
|
|
36a2bcca6e | ||
|
|
19c4b99bae | ||
|
|
33fdd0f051 | ||
|
|
2deaffb4eb | ||
|
|
8996297521 | ||
|
|
0d1cb49cb7 | ||
|
|
8840e9642d | ||
|
|
96c22a5d46 | ||
|
|
121861863f | ||
|
|
c2c8d63848 | ||
|
|
a5d8e88f1d | ||
|
|
ef1c7b6b25 | ||
|
|
03fe730c50 | ||
|
|
a0d3b40548 | ||
|
|
dfba055019 |
4
jikimo_account_process/__init__.py
Normal file
4
jikimo_account_process/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
35
jikimo_account_process/__manifest__.py
Normal file
35
jikimo_account_process/__manifest__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "jikimo_account_process",
|
||||
|
||||
'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': ['base', 'account'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
# 'views/views.xml',
|
||||
# 'views/templates.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
# 'demo/demo.xml',
|
||||
],
|
||||
}
|
||||
3
jikimo_account_process/controllers/__init__.py
Normal file
3
jikimo_account_process/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
21
jikimo_account_process/controllers/controllers.py
Normal file
21
jikimo_account_process/controllers/controllers.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- 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
|
||||
# })
|
||||
4
jikimo_account_process/models/__init__.py
Normal file
4
jikimo_account_process/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import account_move
|
||||
15
jikimo_account_process/models/account_move.py
Normal file
15
jikimo_account_process/models/account_move.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class CustomAccountMoveLine(models.Model):
|
||||
_inherit = 'account.move'
|
||||
_description = "account move line"
|
||||
|
||||
@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)
|
||||
18
jikimo_account_process/models/models.py
Normal file
18
jikimo_account_process/models/models.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- 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
|
||||
@@ -53,6 +53,23 @@ const tableRequiredList = [
|
||||
]
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
// 你可以重写或者添加一些方法和属性
|
||||
async _onDiscardChanges() {
|
||||
// var self = this;
|
||||
@@ -183,17 +200,6 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
|
||||
// })
|
||||
|
||||
$(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
|
||||
|
||||
@@ -324,4 +324,4 @@ def unlink(self):
|
||||
|
||||
|
||||
BaseModel._create = _create
|
||||
BaseModel.unlink = unlink
|
||||
# BaseModel.unlink = unlink
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class MrpProduction(models.Model):
|
||||
@@ -12,7 +12,8 @@ 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
|
||||
@@ -20,3 +21,4 @@ class MrpProduction(models.Model):
|
||||
if wo.current_quality_check_id.component_id:
|
||||
wo.current_quality_check_id._update_component_quantity()
|
||||
return productions
|
||||
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<?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<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<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">-->
|
||||
<!-- <!– 这里放置替换后的内容 –>-->
|
||||
<!-- </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">-->
|
||||
<!-- <!– 这里放置替换后的内容 –>-->
|
||||
<!-- </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>
|
||||
@@ -43,7 +46,9 @@
|
||||
<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>
|
||||
@@ -51,30 +56,33 @@
|
||||
<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>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'sf_base/static/src/scss/*.scss',
|
||||
'sf_base/static/src/js/*.js',
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
@@ -5,3 +5,4 @@ from . import fixture
|
||||
from . import functional_fixture
|
||||
from . import tool_other_features
|
||||
from . import basic_parameters_fixture
|
||||
from . import ir_sequence
|
||||
|
||||
@@ -13,7 +13,7 @@ class BasicParametersFixture(models.Model):
|
||||
diameter = fields.Float('直径(mm)', digits=(16, 2))
|
||||
|
||||
# '零点卡盘' 字段
|
||||
weight = fields.Float('重量(mm)', digits=(16, 2))
|
||||
weight = fields.Float('重量(kg)', digits=(16, 2))
|
||||
orientation_dish_diameter = fields.Float('定位盘直径(mm)', digits=(16, 2))
|
||||
clamping_diameter = fields.Float('装夹直径(mm)', digits=(16, 2))
|
||||
clamping_num = fields.Selection([('1', '1'), ('2', '2'), ('4', '4'), ('6', '6'), ('8', '8')], string='装夹单元数')
|
||||
|
||||
@@ -84,10 +84,12 @@ class MrsProductionProcessCategory(models.Model):
|
||||
class MrsProductionProcess(models.Model):
|
||||
_name = 'sf.production.process'
|
||||
_description = '表面工艺'
|
||||
order = 'sequence asc'
|
||||
|
||||
code = fields.Char("编码")
|
||||
name = fields.Char('名称')
|
||||
remark = fields.Text("备注")
|
||||
sequence = fields.Integer('排序')
|
||||
# processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序')
|
||||
partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂')
|
||||
active = fields.Boolean('有效', default=True)
|
||||
@@ -96,7 +98,7 @@ class MrsProductionProcess(models.Model):
|
||||
# workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_process', required=True)
|
||||
processing_day = fields.Float('加工天数/d')
|
||||
travel_day = fields.Float('路途天数/d')
|
||||
|
||||
sequence = fields.Integer('排序')
|
||||
|
||||
# class MrsProcessingTechnology(models.Model):
|
||||
# _name = 'sf.processing.technology'
|
||||
@@ -148,6 +150,7 @@ class MrsProductionProcessParameter(models.Model):
|
||||
processing_day = fields.Float('加工天数/d')
|
||||
travel_day = fields.Float('路途天数/d')
|
||||
active = fields.Boolean('有效', default=True)
|
||||
processing_mm = fields.Char('加工厚度/mm')
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
|
||||
74
sf_base/models/ir_sequence.py
Normal file
74
sf_base/models/ir_sequence.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import calendar
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class IrSequence(models.Model):
|
||||
_inherit = 'ir.sequence'
|
||||
|
||||
date_range_period = fields.Selection(
|
||||
[('day', '每日'), ('month', '每月'), ('year', '每年')],
|
||||
string='日期期间',
|
||||
)
|
||||
|
||||
def _next(self, sequence_date=None):
|
||||
""" Returns the next number in the preferred sequence in all the ones given in self."""
|
||||
if not self.use_date_range:
|
||||
return self._next_do()
|
||||
# date mode
|
||||
dt = sequence_date or self._context.get('ir_sequence_date', fields.Date.today())
|
||||
seq_date = self.env['ir.sequence.date_range'].search(
|
||||
[
|
||||
('sequence_id', '=', self.id),
|
||||
('date_from', '<=', dt),
|
||||
('date_to', '>=', dt),
|
||||
('date_range_period', '=', self.date_range_period)
|
||||
], limit=1)
|
||||
if not seq_date:
|
||||
if self.date_range_period:
|
||||
seq_date = self._create_date_range_seq_by_period(dt, self.date_range_period)
|
||||
else:
|
||||
seq_date = self._create_date_range_seq(dt)
|
||||
return seq_date.with_context(ir_sequence_date_range=seq_date.date_from)._next()
|
||||
|
||||
def _create_date_range_seq_by_period(self, date, period):
|
||||
if period == 'year':
|
||||
year = fields.Date.from_string(date).strftime('%Y')
|
||||
date_from = '{}-01-01'.format(year)
|
||||
date_to = '{}-12-31'.format(year)
|
||||
if period == 'month':
|
||||
# 计算当前月份的第一天和最后一天
|
||||
year = fields.Date.from_string(date).strftime('%Y')
|
||||
month = fields.Date.from_string(date).strftime('%m')
|
||||
date_from = fields.Date.from_string(date).strftime('%Y-%m-01')
|
||||
date_to = '{}-{}-{}'.format(year, month, calendar.monthrange(int(year), int(month))[1])
|
||||
if period == 'day':
|
||||
date_from = date
|
||||
date_to = date
|
||||
date_range = self.env['ir.sequence.date_range'].search(
|
||||
[
|
||||
('sequence_id', '=', self.id),
|
||||
('date_to', '>=', date_from),
|
||||
('date_to', '<=', date),
|
||||
('date_range_period', '=', period)
|
||||
],
|
||||
order='date_to desc', limit=1)
|
||||
if date_range:
|
||||
date_from = date_range.date_to + timedelta(days=1)
|
||||
seq_date_range = self.env['ir.sequence.date_range'].sudo().create({
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'sequence_id': self.id,
|
||||
'date_range_period': period,
|
||||
})
|
||||
return seq_date_range
|
||||
|
||||
|
||||
class IrSequenceDateRange(models.Model):
|
||||
_inherit = 'ir.sequence.date_range'
|
||||
|
||||
date_range_period = fields.Selection(
|
||||
[('day', '每日'), ('month', '每月'), ('year', '每年')],
|
||||
string='日期期间',
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
diff a/sf_base/models/tool_base_new.py b/sf_base/models/tool_base_new.py (rejected hunks)
|
||||
@@ -108,6 +108,4 @@
|
||||
cutting_speed_ids = fields.One2many('sf.cutting.speed', 'standard_library_id', string='切削速度Vc')
|
||||
- feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz',
|
||||
- domain=[('cutting_speed', '!=', False)])
|
||||
- feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz',
|
||||
- domain=[('cutting_speed', '!=', False)])
|
||||
+ feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz')
|
||||
+ feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz')
|
||||
|
||||
61
sf_base/static/src/js/custom_barcode_handlers.js
Normal file
61
sf_base/static/src/js/custom_barcode_handlers.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { barcodeGenericHandlers } from '@barcodes/barcode_handlers';
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
// 定义新的 clickOnButton 函数
|
||||
function customClickOnButton(selector) {
|
||||
console.log("This is the custom clickOnButton function!");
|
||||
|
||||
const buttons = document.body.querySelectorAll(selector);
|
||||
|
||||
let length = buttons.length;
|
||||
if (length > 0) {
|
||||
buttons[length - 1].click();
|
||||
} else {
|
||||
console.warn(`Button with selector ${selector} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
patch(barcodeGenericHandlers, "start", {
|
||||
start(env, { ui, barcode, notification }) {
|
||||
// 使用新定义的 clickOnButton 函数
|
||||
const COMMANDS = {
|
||||
"O-CMD.EDIT": () => customClickOnButton(".o_form_button_edit"),
|
||||
"O-CMD.DISCARD": () => customClickOnButton(".o_form_button_cancel"),
|
||||
"O-CMD.SAVE": () => customClickOnButton(".o_form_button_save"),
|
||||
"O-CMD.PREV": () => customClickOnButton(".o_pager_previous"),
|
||||
"O-CMD.NEXT": () => customClickOnButton(".o_pager_next"),
|
||||
"O-CMD.PAGER-FIRST": () => updatePager("first"),
|
||||
"O-CMD.PAGER-LAST": () => updatePager("last"),
|
||||
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
|
||||
};
|
||||
|
||||
barcode.bus.addEventListener("barcode_scanned", (ev) => {
|
||||
const barcode = ev.detail.barcode;
|
||||
if (barcode.startsWith("O-BTN.")) {
|
||||
let targets = [];
|
||||
try {
|
||||
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
|
||||
} catch (_e) {
|
||||
console.warn(`Barcode '${barcode}' is not valid`);
|
||||
}
|
||||
for (let elem of targets) {
|
||||
elem.click();
|
||||
}
|
||||
}
|
||||
if (barcode.startsWith("O-CMD.")) {
|
||||
const fn = COMMANDS[barcode];
|
||||
if (fn) {
|
||||
fn();
|
||||
} else {
|
||||
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
|
||||
title: env._t("Unknown barcode command"),
|
||||
type: "danger"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
42
sf_base/static/src/js/remove_focus.js
Normal file
42
sf_base/static/src/js/remove_focus.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from '@web/core/registry';
|
||||
|
||||
import { formView } from '@web/views/form/form_view';
|
||||
import { FormController } from '@web/views/form/form_controller';
|
||||
|
||||
import { listView } from '@web/views/list/list_view';
|
||||
import { ListController } from '@web/views/list/list_controller'
|
||||
|
||||
import { onRendered, onMounted } from "@odoo/owl";
|
||||
|
||||
export class RemoveFocusFormController extends FormController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
onMounted(() => {
|
||||
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_form_view', {
|
||||
...formView,
|
||||
Controller: RemoveFocusFormController,
|
||||
});
|
||||
|
||||
|
||||
export class RemoveFocusListController extends ListController {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
onMounted(() => {
|
||||
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registry.category('views').add('remove_focus_list_view', {
|
||||
...listView,
|
||||
Controller: RemoveFocusListController,
|
||||
});
|
||||
@@ -12,11 +12,10 @@
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
|
||||
<field name="model">sf.production.process.parameter</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="表面工艺可选参数" create="0" delete="0">
|
||||
<form string="表面工艺可选参数" create="0" delete="0" >
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
@@ -26,18 +25,19 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="process_id" readonly="1"/>
|
||||
<field name="process_description" readonly="1"/>
|
||||
<field name="process_id" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="process_description" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="gain_way"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="processing_day" readonly="1"/>
|
||||
<field name="travel_day" readonly="1"/>
|
||||
<field name="processing_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="travel_day" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
<field name="processing_mm" attrs="{'readonly': [('code', '!=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="适用材料">
|
||||
<field name="materials_model_ids"></field>
|
||||
<field name="materials_model_ids" attrs="{'readonly': [('code', '!=', False)]}"></field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
@@ -52,7 +52,7 @@
|
||||
<search>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
<field name="name" string="名称" filter_domain="[('name','ilike',self)]"/>
|
||||
<field name="code" string="编码" filter_domain="[('codeNum','ilike',self)]"/>
|
||||
<field name="code" string="编码" filter_domain="[('code','ilike',self)]"/>
|
||||
<searchpanel class="account_root">
|
||||
<field name="process_id" icon="fa-filter"/>
|
||||
</searchpanel>
|
||||
@@ -140,7 +140,7 @@
|
||||
<field name="model">sf.production.process.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="表面工艺类别" default_order="sequence, id" create="0" edit="0" delete="1">
|
||||
<field name="sequence" widget="handle" string="序号"/>
|
||||
<field name="sequence" widget="handle" string="序号" readonly="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name" string="名称"/>
|
||||
</tree>
|
||||
@@ -163,7 +163,8 @@
|
||||
<record model="ir.ui.view" id="sf_production_process_tree">
|
||||
<field name="model">sf.production.process</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="表面工艺" create="0" edit="0" delete="1">
|
||||
<tree string="表面工艺" create="0" edit="0" delete="0">
|
||||
<field name="sequence" string="加工顺序" readonly="1"/>
|
||||
<field name="code"/>
|
||||
<field name="name" string="名称"/>
|
||||
<field name="remark"/>
|
||||
@@ -174,7 +175,7 @@
|
||||
<record model="ir.ui.view" id="sf_production_process_form">
|
||||
<field name="model">sf.production.process</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="表面工艺" create="0" edit="1" delete="1">
|
||||
<form string="表面工艺" create="0" delete="0">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
@@ -192,11 +193,11 @@
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="可选参数">
|
||||
<field name="parameter_ids">
|
||||
<tree force_save="1">
|
||||
<page string="可选参数" >
|
||||
<field name="parameter_ids" >
|
||||
<tree force_save="1" create="0">
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name"/>
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="gain_way"/>
|
||||
<field name='process_id' default="default"/>
|
||||
</tree>
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="diameter"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="orientation_dish_diameter"/>
|
||||
<field name="clamping_diameter"/>
|
||||
<field name="clamping_num"/>
|
||||
@@ -197,7 +197,7 @@
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="diameter"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="clamping_diameter"/>
|
||||
<field name="connector_diameter"/>
|
||||
<field name="chucking_power_max"/>
|
||||
@@ -220,7 +220,7 @@
|
||||
<field name="length"/>
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="gripper_length_min"/>
|
||||
<field name="gripper_width_min"/>
|
||||
<field name="gripper_height_min"/>
|
||||
@@ -248,7 +248,7 @@
|
||||
<field name="length"/>
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="gripper_length_min"/>
|
||||
<field name="gripper_width_min"/>
|
||||
<field name="gripper_height_min"/>
|
||||
@@ -278,7 +278,7 @@
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="height_tolerance_value"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="gripper_length_min"/>
|
||||
<field name="gripper_width_min"/>
|
||||
<field name="gripper_height_min"/>
|
||||
@@ -307,7 +307,7 @@
|
||||
<field name="length"/>
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="gripper_length_min"/>
|
||||
<field name="gripper_width_min"/>
|
||||
<field name="gripper_height_min"/>
|
||||
@@ -335,7 +335,7 @@
|
||||
<field name="width"/>
|
||||
<field name="height"/>
|
||||
<field name="diameter"/>
|
||||
<field name="weight"/>
|
||||
<field name="weight" string="重量(kg)"/>
|
||||
<field name="gripper_length_min"/>
|
||||
<field name="gripper_width_min"/>
|
||||
<field name="gripper_height_min"/>
|
||||
|
||||
@@ -80,10 +80,10 @@
|
||||
<field name="name">sf.cutter.function.tree</field>
|
||||
<field name="model">sf.functional.cutting.tool.model</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="功能刀具类型" create="0" delete="0" edit="1">
|
||||
<field name="name" string="名称"/>
|
||||
<field name="code"/>
|
||||
<field name="remark"/>
|
||||
<tree string="功能刀具类型" create="0" delete="0" edit="1" editable="bottom">
|
||||
<field name="name" string="名称" readonly="1"/>
|
||||
<field name="code" readonly="1"/>
|
||||
<field name="remark" readonly="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -36,7 +36,7 @@ class Http(models.AbstractModel):
|
||||
post_time = int(datas['HTTP_TIMESTAMP'])
|
||||
datetime_post = datetime.fromtimestamp(post_time)
|
||||
datetime_now = datetime.now().replace(microsecond=0)
|
||||
datetime_del = datetime_now + timedelta(seconds=5)
|
||||
datetime_del = datetime_now + timedelta(seconds=30)
|
||||
if datetime_post > datetime_del:
|
||||
raise AuthenticationError('请求已过期')
|
||||
check_str = '%s%s%s' % (datas['HTTP_TOKEN'], post_time, factory_secret.sf_secret_key)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import requests
|
||||
from odoo import fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import fields, models, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -14,26 +16,52 @@ class StatusChange(models.Model):
|
||||
def action_confirm(self):
|
||||
# 在原有方法执行前记录日志和执行其他操作
|
||||
logging.info('函数已经执行=============')
|
||||
server_product_none = []
|
||||
for order in self.order_line:
|
||||
gain_way_no = order.product_template_id.model_process_parameters_ids.filtered(lambda a: not a.gain_way)
|
||||
if gain_way_no:
|
||||
process_parameters = [item.name for item in gain_way_no]
|
||||
raise UserError(
|
||||
_("请先至【制造】-【配置】中【表面工艺可选参数】为【%s】填写获取方式", ", ".join(process_parameters)))
|
||||
for item in order.product_template_id.model_process_parameters_ids:
|
||||
if item.gain_way == '外协':
|
||||
server_product = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', item.id),
|
||||
('detailed_type', '=', 'service')])
|
||||
if not server_product:
|
||||
server_product_none.append(item.name)
|
||||
if server_product_none:
|
||||
raise UserError(_("请先至【产品】中创建【表面工艺参数】为【%s】的服务产品", ", ".join(server_product_none)))
|
||||
|
||||
# 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法)
|
||||
res = super(StatusChange, self).action_confirm()
|
||||
|
||||
# 原有方法执行后,进行额外的操作(如调用外部API)
|
||||
process_start_time = str(datetime.now())
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
json1 = {
|
||||
'params': {
|
||||
'model_name': 'jikimo.process.order',
|
||||
'field_name': 'name',
|
||||
'default_code': self.default_code,
|
||||
'state': '加工中',
|
||||
'process_start_time': process_start_time,
|
||||
},
|
||||
}
|
||||
url1 = config['bfm_url_new'] + '/api/get/state/get_order'
|
||||
requests.post(url1, json=json1, data=None)
|
||||
logging.info('接口已经执行=============')
|
||||
|
||||
try:
|
||||
res = super(StatusChange, self).action_confirm()
|
||||
# 原有方法执行后,进行额外的操作(如调用外部API)
|
||||
process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
json1 = {
|
||||
'params': {
|
||||
'model_name': 'jikimo.process.order',
|
||||
'field_name': 'name',
|
||||
'default_code': self.default_code,
|
||||
'state': '加工中',
|
||||
'process_start_time': process_start_time,
|
||||
},
|
||||
}
|
||||
url1 = config['bfm_url_new'] + '/api/get/state/get_order'
|
||||
ret = requests.post(url1, json=json1, data=None)
|
||||
ret = ret.json()
|
||||
if not ret.get('error'):
|
||||
logging.info('接口已经执行=============')
|
||||
else:
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("bfm订单状态同步失败:%s request info %s" % traceback_error)
|
||||
logging.error('/api/get/state/get_order 请求失败{}'.format(ret))
|
||||
raise UserError('工厂加工同步订单状态到bfm失败')
|
||||
except UserError as e:
|
||||
traceback_error = traceback.format_exc()
|
||||
logging.error("工厂加工同步订单状态失败:%s " % traceback_error)
|
||||
raise UserError(e)
|
||||
return res
|
||||
|
||||
def action_cancel(self):
|
||||
@@ -202,12 +230,12 @@ class FinishStatusChange(models.Model):
|
||||
[('id', 'child_of', self.picking_type_id.warehouse_id.view_location_id.id),
|
||||
('usage', '!=', 'supplier')])
|
||||
if self.env['stock.move'].search([
|
||||
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
|
||||
('product_qty', '>', 0),
|
||||
('location_id', 'in', wh_location_ids),
|
||||
('move_orig_ids', '=', False),
|
||||
('picking_id', 'not in', self.ids),
|
||||
('product_id', 'in', lines.product_id.ids)], limit=1):
|
||||
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
|
||||
('product_qty', '>', 0),
|
||||
('location_id', 'in', wh_location_ids),
|
||||
('move_orig_ids', '=', False),
|
||||
('picking_id', 'not in', self.ids),
|
||||
('product_id', 'in', lines.product_id.ids)], limit=1):
|
||||
action = self.action_view_reception_report()
|
||||
action['context'] = {'default_picking_ids': self.ids}
|
||||
return action
|
||||
|
||||
@@ -122,7 +122,7 @@ class ResMrpBomMo(models.Model):
|
||||
# 查bom的原材料
|
||||
def get_raw_bom(self, product):
|
||||
raw_bom = self.env['product.product'].search(
|
||||
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)])
|
||||
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)],limit=1)
|
||||
return raw_bom
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="mrp.product_template_action" model="ir.actions.act_window">
|
||||
<field name="context">
|
||||
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="view_sale_product_template_form_inherit_sf">
|
||||
<field name="name">product.template.form.inherit.sf</field>
|
||||
<field name="model">product.template</field>
|
||||
@@ -88,7 +93,8 @@
|
||||
</div>
|
||||
<field name="model_volume" string="体积[mm³]"/>
|
||||
<field name="product_model_type_id" string="模型类型"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" readonly="1"/>
|
||||
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"
|
||||
readonly="1"/>
|
||||
<field name="model_machining_precision"/>
|
||||
<field name="model_process_parameters_ids" string="表面工艺参数"
|
||||
widget="many2many_tags"
|
||||
@@ -103,11 +109,11 @@
|
||||
'刀具')], 'required': True}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
<!-- </attribute>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
|
||||
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
|
||||
<!-- [('product_variant_count', '>' , 1)]}-->
|
||||
<!-- </attribute>-->
|
||||
<!-- </xpath>-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
3
sf_hr/__init__.py
Normal file
3
sf_hr/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
24
sf_hr/__manifest__.py
Normal file
24
sf_hr/__manifest__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- 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': ['hr'],
|
||||
'data': [
|
||||
'views/hr_employee.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'data/cron_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
'qweb': [
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
15
sf_hr/data/cron_data.xml
Normal file
15
sf_hr/data/cron_data.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="ir.cron" id="ir_cron_employee_info_sync">
|
||||
<field name="name">员工企微id同步</field>
|
||||
<field name="model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._employee_info_sync()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
4
sf_hr/models/__init__.py
Normal file
4
sf_hr/models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hr_employee
|
||||
from . import res_config_setting
|
||||
26
sf_hr/models/hr_employee.py
Normal file
26
sf_hr/models/hr_employee.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import logging
|
||||
import requests
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JkmPracticeEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
_description = '员工信息'
|
||||
|
||||
we_id = fields.Char(string='企微ID', index=True)
|
||||
|
||||
def _employee_info_sync(self):
|
||||
url = '/api/get/organization'
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
ret = requests.post((config['ims_url'] + url), json={}, data={})
|
||||
result = ret.json()['result']
|
||||
if result['code'] == 200:
|
||||
if result['employee_list']:
|
||||
for employee_info in result['employee_list']:
|
||||
if employee_info['work_email']:
|
||||
self.sudo().search([('work_email', '=', employee_info['work_email'])]).write(
|
||||
{'we_id': employee_info['we_id']})
|
||||
else:
|
||||
logging.info('_employee_info_sync error:%s' % result['message'])
|
||||
30
sf_hr/models/res_config_setting.py
Normal file
30
sf_hr/models/res_config_setting.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResIMSConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
ims_url = fields.Char('综合管理系统访问地址')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
"""
|
||||
重载获取参数的方法,参数都存在系统参数中
|
||||
:return:
|
||||
"""
|
||||
values = super(ResIMSConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
ims_url = config.get_param('ims_url', default='')
|
||||
values.update(
|
||||
ims_url=ims_url,
|
||||
)
|
||||
return values
|
||||
|
||||
def set_values(self):
|
||||
super(ResIMSConfigSettings, self).set_values()
|
||||
ir_config = self.env['ir.config_parameter'].sudo()
|
||||
ir_config.set_param("ims_url", self.ims_url or "")
|
||||
0
sf_hr/security/ir.model.access.csv
Normal file
0
sf_hr/security/ir.model.access.csv
Normal file
|
|
24
sf_hr/views/hr_employee.xml
Normal file
24
sf_hr/views/hr_employee.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_form_employee_extend" model="ir.ui.view">
|
||||
<field name="name">employee_form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group//field[@name='work_email']" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='public']" position='after'>
|
||||
<page string="企业微信">
|
||||
<group col="2">
|
||||
<group>
|
||||
<field name="we_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
28
sf_hr/views/res_config_settings_views.xml
Normal file
28
sf_hr/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_finance_view_form_extend" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.finance.view.form.extend</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" position="before">
|
||||
<div>
|
||||
<h2>综合管理系统接口配置</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="ims_url"/>
|
||||
<field name="ims_url"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -30,6 +30,7 @@
|
||||
'views/machine_info_present.xml',
|
||||
'views/delivery_record.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/maintenance_views.xml',
|
||||
|
||||
],
|
||||
'assets': {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,3 +2,4 @@ from . import ftp_client
|
||||
from . import ftp_operate
|
||||
from . import py2opcua
|
||||
from . import res_config_setting
|
||||
from . import mrp_workorder
|
||||
|
||||
@@ -121,6 +121,13 @@ class Machine_ftp(models.Model):
|
||||
"""
|
||||
_inherit = 'maintenance.equipment'
|
||||
|
||||
# 机床首次上线时间(默认取值2024年08月01日零点)
|
||||
|
||||
def _get_default_online_time(self):
|
||||
return datetime(2024, 1, 1, 0, 0, 0)
|
||||
|
||||
first_online_time = fields.Datetime(string='首次上线时间', default=_get_default_online_time)
|
||||
|
||||
# workorder_ids = fields.One2many('mrp.workorder', 'machine_tool_id', string='工单')
|
||||
|
||||
# # 机床配置项目
|
||||
@@ -275,7 +282,28 @@ class Machine_ftp(models.Model):
|
||||
alarm_msg = fields.Char('故障报警信息', readonly=True)
|
||||
clear_time = fields.Char('故障消除时间(复原时间)', readonly=True)
|
||||
|
||||
# 当前程序名, 机床累计运行时间, 机床系统日期, 机床系统时间, 当前刀具号, 机床循环时间
|
||||
# # 开动率
|
||||
run_rate = fields.Char('开动率', readonly=True)
|
||||
|
||||
# 同步CNC设备到oee
|
||||
def sync_oee(self):
|
||||
"""
|
||||
同步CNC设备到oee
|
||||
:return:
|
||||
"""
|
||||
for record in self:
|
||||
record.ensure_one()
|
||||
cnc_oee_dict = {
|
||||
'equipment_id': record.id,
|
||||
'type_id': record.type_id.id,
|
||||
'machine_tool_picture': record.machine_tool_picture,
|
||||
'equipment_code': record.code,
|
||||
'function_type': record.function_type,
|
||||
}
|
||||
if self.env['maintenance.equipment.oee.logs'].search([('equipment_id', '=', record.id)]):
|
||||
self.env['maintenance.equipment.oee.logs'].write(cnc_oee_dict)
|
||||
else:
|
||||
self.env['maintenance.equipment.oee.logs'].create(cnc_oee_dict)
|
||||
|
||||
|
||||
class WorkCenterBarcode(models.Model):
|
||||
|
||||
38
sf_machine_connect/models/mrp_workorder.py
Normal file
38
sf_machine_connect/models/mrp_workorder.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import re
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class ResMrpWorkOrder(models.Model):
|
||||
_inherit = 'mrp.workorder'
|
||||
|
||||
mixed_search_field = fields.Char(string='坯料产品名称/RFID')
|
||||
|
||||
@api.model
|
||||
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
|
||||
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
|
||||
domain = domain or []
|
||||
for index, item in enumerate(domain):
|
||||
if isinstance(item, list):
|
||||
if item[0] == 'mixed_search_field':
|
||||
if self._is_rfid_code(item[2]):
|
||||
domain[index] = ['rfid_code', item[1], item[2]]
|
||||
else:
|
||||
domain[index] = ['product_tmpl_name', item[1], item[2]]
|
||||
|
||||
return super(ResMrpWorkOrder, self).web_read_group(domain, fields, groupby, limit=limit, offset=offset, orderby=orderby,
|
||||
lazy=lazy, expand=expand, expand_limit=expand_limit, expand_orderby=expand_orderby)
|
||||
|
||||
def _is_rfid_code(self, tag):
|
||||
"""
|
||||
判断是否是rfid_code
|
||||
"""
|
||||
# 基于长度判断(假设RFID标签长度为10到16个字符)
|
||||
if not 10 <= len(tag) <= 16:
|
||||
return False
|
||||
|
||||
# 基于字符集判断(仅包含数字和字母)
|
||||
if not re.match("^[0-9]*$", tag):
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -16,6 +16,7 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
# ("https://bfm.jikimo.com", "正式环境(https://bfm.jikimo.com)")], string='bfm环境', store=True)
|
||||
|
||||
bfm_url_new = fields.Char('业务平台环境路径', placeholder='请输入当前对应的业务平台环境路径')
|
||||
get_check_file_path = fields.Char('获取检查文件路径', default='')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
@@ -26,9 +27,11 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
values = super(ResBFMConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
bfm_url_new = config.get_param('bfm_url_new', default='')
|
||||
get_check_file_path = config.get_param('get_check_file_path', default='')
|
||||
|
||||
values.update(
|
||||
bfm_url_new=bfm_url_new,
|
||||
get_check_file_path=get_check_file_path
|
||||
)
|
||||
return values
|
||||
|
||||
@@ -36,3 +39,4 @@ class ResBFMConfigSettings(models.TransientModel):
|
||||
super(ResBFMConfigSettings, self).set_values()
|
||||
ir_config = self.env['ir.config_parameter'].sudo()
|
||||
ir_config.set_param("bfm_url_new", self.bfm_url_new or "")
|
||||
ir_config.set_param("get_check_file_path", self.get_check_file_path or "")
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<filter string="自动编程" name="no_manual_quotation" domain="[('manual_quotation', '=', False)]"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='production_id']" position="before">
|
||||
<field name="mixed_search_field"/>
|
||||
<field name="product_tmpl_name"/>
|
||||
<field name="rfid_code"/>
|
||||
</xpath>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<field name="run_status"/>
|
||||
<field name="run_time"/>
|
||||
<field name="system_date"/>
|
||||
<field name="first_online_time"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="cut_status"/>
|
||||
|
||||
17
sf_machine_connect/views/maintenance_views.xml
Normal file
17
sf_machine_connect/views/maintenance_views.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<!-- 修改设备列表视图-->
|
||||
<record id="sf_machine_hr_equipment_view_tree_inherit" model="ir.ui.view">
|
||||
<field name="name">sf.machine.hr.equipment.view.tree.inherit</field>
|
||||
<field name="model">maintenance.equipment</field>
|
||||
<field name="inherit_id" ref="maintenance.hr_equipment_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="inside">
|
||||
<header>
|
||||
<button name="sync_oee" type="object" string="同步设备至OEE"/>
|
||||
</header>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -22,6 +22,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>获取检测报告服务配置</h2>
|
||||
<div class="row mt16 o_settings_container" id="check_report_config">
|
||||
<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="get_check_file_path" />
|
||||
<field name="get_check_file_path" string="检测报告服务地址"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='check_report_config']/div" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="is_get_detection_file"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="is_get_detection_file"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -24,6 +24,7 @@ class SfEquipmentSaintenanceStandards(models.Model):
|
||||
remark = fields.Char('备注')
|
||||
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
|
||||
name = fields.Char(string='名称')
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
||||
@@ -38,8 +38,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
|
||||
crea_url = "/api/machine_tool/create"
|
||||
|
||||
|
||||
#AGV运行日志
|
||||
# AGV运行日志
|
||||
agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志')
|
||||
# 1212修改后的字段
|
||||
number_of_axles = fields.Selection(
|
||||
@@ -117,7 +116,6 @@ class SfMaintenanceEquipment(models.Model):
|
||||
# num = "%04d" % m
|
||||
# return num
|
||||
|
||||
|
||||
equipment_maintenance_standards_ids = fields.Many2many('equipment.maintenance.standards',
|
||||
'sf_maintenance_equipment_ids', string='设备维保标准')
|
||||
eq_maintenance_id = fields.Many2one('equipment.maintenance.standards', string='设备保养标准',
|
||||
@@ -179,7 +177,8 @@ class SfMaintenanceEquipment(models.Model):
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '型号')
|
||||
|
||||
state = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
default='正常', string="机床状态")
|
||||
run_time = fields.Char('总运行时长')
|
||||
# 0606新增字段
|
||||
@@ -328,7 +327,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
item.tool_diameter_min = item.type_id.tool_diameter_min
|
||||
item.machine_tool_category = item.type_id.machine_tool_category.id
|
||||
item.brand_id = item.type_id.brand_id.id
|
||||
#新增修改字段
|
||||
# 新增修改字段
|
||||
item.taper_type_id = item.type_id.taper_type_id.id
|
||||
item.function_type = item.type_id.function_type
|
||||
item.a_axis = item.type_id.a_axis
|
||||
@@ -370,7 +369,6 @@ class SfMaintenanceEquipment(models.Model):
|
||||
item.image_id = item.type_id.jg_image_id.ids
|
||||
item.image_lq_id = item.type_id.lq_image_id.ids
|
||||
|
||||
|
||||
# AGV小车设备参数
|
||||
AGV_L = fields.Char('AGV尺寸(长)')
|
||||
AGV_W = fields.Char('AGV尺寸(宽)')
|
||||
@@ -461,18 +459,6 @@ class SfMaintenanceEquipment(models.Model):
|
||||
original_value = fields.Char('原值')
|
||||
incomplete_value = fields.Char('残值')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 注册同步机床
|
||||
def enroll_machine_tool(self):
|
||||
sf_sync_config = self.env['res.config.settings'].get_values()
|
||||
@@ -763,7 +749,7 @@ class SfMaintenanceEquipment(models.Model):
|
||||
image_id = fields.Many2many('maintenance.equipment.image', 'equipment_id',
|
||||
domain="[('type', '=', '加工能力')]")
|
||||
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
|
||||
domain="[('type', '=', '冷却方式')]")
|
||||
domain="[('type', '=', '冷却方式')]")
|
||||
|
||||
|
||||
class SfRobotAxisNum(models.Model):
|
||||
@@ -777,4 +763,5 @@ class SfRobotAxisNum(models.Model):
|
||||
weight = fields.Char('最大负载(kg)')
|
||||
permissible_load_torque = fields.Char('允许负载扭矩(N-m)')
|
||||
permissible_inertial_torque = fields.Char('允许惯性扭矩(kg-m²)')
|
||||
equipment_id = fields.Many2one('maintenance.equipment', string='机器人', domain="[('equipment_type', '=', '机器人')]")
|
||||
equipment_id = fields.Many2one('maintenance.equipment', string='机器人',
|
||||
domain="[('equipment_type', '=', '机器人')]")
|
||||
|
||||
@@ -41,29 +41,32 @@ class SfMaintenanceEquipmentOEELog(models.Model):
|
||||
_name = 'maintenance.equipment.oee.logs'
|
||||
_description = '设备运行日志'
|
||||
|
||||
equipment_id = fields.Many2one('maintenance.equipment', '机台号')
|
||||
equipment_code = fields.Char('设备编码')
|
||||
equipment_id = fields.Many2one('maintenance.equipment', '机台号', readonly='True')
|
||||
equipment_code = fields.Char('设备编码', readonly='True')
|
||||
name = fields.Char('设备名称', readonly='True')
|
||||
function_type = fields.Selection(
|
||||
[("ZXJGZX", "钻铣加工中心"), ("CXJGZX", "车削加工中心"), ("FHJGZX", "复合加工中心")],
|
||||
default="", string="功能类型")
|
||||
machine_tool_picture = fields.Binary('设备图片')
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号')
|
||||
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', reaonly='True')
|
||||
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
|
||||
("检修", "检修"), ("保养", "保养")], default="", string="实时状态")
|
||||
online_time = fields.Char('开机时长')
|
||||
online_time = fields.Char('开机时长', reaonly='True')
|
||||
|
||||
offline_time = fields.Char('关机时长')
|
||||
offline_nums = fields.Integer('关机次数')
|
||||
offline_time = fields.Char('关机时长', reaonly='True')
|
||||
offline_nums = fields.Integer('关机次数', reaonly='True')
|
||||
# 待机时长
|
||||
|
||||
idle_time = fields.Char('待机时长')
|
||||
idle_time = fields.Char('待机时长', reaonly='True')
|
||||
|
||||
# 待机率
|
||||
idle_rate = fields.Char('待机率')
|
||||
idle_rate = fields.Char('待机率', reaonly='True')
|
||||
|
||||
work_time = fields.Char('加工时长')
|
||||
work_rate = fields.Char('可用率')
|
||||
fault_time = fields.Char('故障时长')
|
||||
fault_rate = fields.Char('故障率')
|
||||
fault_nums = fields.Integer('故障次数')
|
||||
work_time = fields.Char('加工时长', reaonly='True')
|
||||
work_rate = fields.Char('可用率', reaonly='True')
|
||||
fault_time = fields.Char('故障时长', reaonly='True')
|
||||
fault_rate = fields.Char('故障率', reaonly='True')
|
||||
fault_nums = fields.Integer('故障次数', reaonly='True')
|
||||
|
||||
detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情')
|
||||
|
||||
@@ -81,12 +84,15 @@ class SfMaintenanceEquipmentOEELog(models.Model):
|
||||
class SfMaintenanceEquipmentOEELogDetail(models.Model):
|
||||
_name = 'maintenance.equipment.oee.log.detail'
|
||||
_description = '设备运行日志详情'
|
||||
_order = 'time desc'
|
||||
|
||||
sequence = fields.Integer('序号')
|
||||
# sequence = fields.Integer('序号', related='id')
|
||||
time = fields.Datetime('时间')
|
||||
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
|
||||
("检修", "检修"), ("保养", "保养")], default="", string="事件/状态")
|
||||
production_id = fields.Many2one('mrp.production', '加工工单')
|
||||
production_name = fields.Char('加工工单')
|
||||
|
||||
log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志')
|
||||
# equipment_code = fields.Char('设备编码', related='log_id.equipment_code')
|
||||
equipment_code = fields.Char('设备编码', readonly='True')
|
||||
|
||||
|
||||
@@ -6,15 +6,16 @@
|
||||
<field name="name">equipment.maintenance.standards.form</field>
|
||||
<field name="model">equipment.maintenance.standards</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="设备维保标准">
|
||||
<form string="设备维保标准" delete="false" duplicate="false">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_equipment_category_id" required="1" />
|
||||
<field name="eq_maintenance_ids" invisible='1'/>
|
||||
<field name="overhaul_ids" invisible='1'/>
|
||||
<group>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="name" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_equipment_category_id" required="1"/>
|
||||
<field name="eq_maintenance_ids" invisible='1'/>
|
||||
<field name="overhaul_ids" invisible='1'/>
|
||||
|
||||
|
||||
</group>
|
||||
@@ -50,7 +51,8 @@
|
||||
<field name="name">equipment.maintenance.standards.tree</field>
|
||||
<field name="model">equipment.maintenance.standards</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="设备维保标准">
|
||||
<tree string="设备维保标准" delete="false">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="code" readonly="1" force_save="1"/>
|
||||
<field name="maintenance_type" required="1"/>
|
||||
<field name="name" required="1"/>
|
||||
@@ -77,6 +79,7 @@
|
||||
<field name="name" string="日常机床保养"/>
|
||||
<field name="created_user_id" string="创建人"/>
|
||||
<field name="maintenance_equipment_category_id" string="设备类别"/>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
<field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
|
||||
<field name="type_id"/>
|
||||
<field name="state"/>
|
||||
<field name="equipment_code"/>
|
||||
<field name="function_type"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
@@ -202,10 +204,10 @@
|
||||
<!-- <field name="detail_ids" domain="[('time','<',(datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'))]"> -->
|
||||
<field name="detail_ids" domain="[('state','ilike','加工')]">
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
<field name="state"/>
|
||||
<field name="production_id"/>
|
||||
<field name="production_name"/>
|
||||
</tree>
|
||||
<!-- <form> -->
|
||||
<!-- <field name="sequence"/> -->
|
||||
@@ -219,10 +221,10 @@
|
||||
<page string="历史日志详情">
|
||||
<field name="detail_ids">
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
<field name="state"/>
|
||||
<field name="production_id"/>
|
||||
<field name="production_name"/>
|
||||
</tree>
|
||||
<!-- <form> -->
|
||||
<!-- <field name="sequence"/> -->
|
||||
@@ -263,10 +265,10 @@
|
||||
<field name="model">maintenance.equipment.oee.log.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
<field name="state"/>
|
||||
<field name="production_id"/>
|
||||
<field name="production_name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -280,10 +282,10 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="state"/>
|
||||
<field name="production_id"/>
|
||||
<!-- <field name="production_id"/> -->
|
||||
</group>
|
||||
<group>
|
||||
<field name="sequence"/>
|
||||
<!-- <field name="sequence"/> -->
|
||||
<field name="time"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
'data/stock_data.xml',
|
||||
'data/empty_racks_data.xml',
|
||||
'data/panel_data.xml',
|
||||
'data/agv_scheduling_data.xml',
|
||||
'security/group_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/workpiece_delivery_views.xml',
|
||||
'wizard/rework_wizard_views.xml',
|
||||
'wizard/production_wizard_views.xml',
|
||||
'views/mrp_views_menus.xml',
|
||||
'views/agv_scheduling_views.xml',
|
||||
'views/stock_lot_views.xml',
|
||||
'views/mrp_production_addional_change.xml',
|
||||
'views/mrp_routing_workcenter_view.xml',
|
||||
@@ -30,7 +32,7 @@
|
||||
'views/model_type_view.xml',
|
||||
'views/agv_setting_views.xml',
|
||||
'views/sf_maintenance_equipment.xml',
|
||||
|
||||
'views/res_config_settings_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
|
||||
@@ -40,7 +42,9 @@
|
||||
'web.assets_backend': [
|
||||
'sf_manufacturing/static/src/xml/kanban_change.xml',
|
||||
'sf_manufacturing/static/src/js/kanban_change.js',
|
||||
'sf_manufacturing/static/src/scss/kanban_change.scss'
|
||||
'sf_manufacturing/static/src/scss/kanban_change.scss',
|
||||
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
|
||||
'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js',
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import logging
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
@@ -145,7 +147,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('get_qcCheck error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def button_Work_START(self, **kw):
|
||||
"""
|
||||
@@ -193,7 +195,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('button_Work_START error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def button_Work_End(self, **kw):
|
||||
"""
|
||||
@@ -244,7 +246,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('button_Work_End error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def PartQualityInspect(self, **kw):
|
||||
"""
|
||||
@@ -290,7 +292,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('PartQualityInspect error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def CMMProgDolod(self, **kw):
|
||||
"""
|
||||
@@ -330,7 +332,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('CMMProgDolod error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def NCProgDolod(self, **kw):
|
||||
"""
|
||||
@@ -386,7 +388,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
ret = json.loads(datas)
|
||||
request.env['center_control.interface.log'].sudo().create(
|
||||
{'content': ret, 'name': 'AutoDeviceApi/LocationChange'})
|
||||
logging.info('LocationChange_ret===========:%s' % ret)
|
||||
logging.info('库位变更LocationChange_ret:%s' % ret)
|
||||
RfidCode = ret['RfidCode']
|
||||
ChangeType = ret['ChangeType']
|
||||
OldDeciveId = ret['OldDeciveId']
|
||||
@@ -396,40 +398,86 @@ class Manufacturing_Connect(http.Controller):
|
||||
OldDeciveStart = ret['OldDeciveStart']
|
||||
OldDeciveEnd = ret['OldDeciveEnd']
|
||||
|
||||
temp_val_sn_id = None
|
||||
old_localtion = None
|
||||
# if ChangeType == 'Part' or ChangeType == 'Tool':
|
||||
stock_lot_obj = request.env['stock.lot'].sudo().search(
|
||||
[('rfid', '=', RfidCode)], limit=1)
|
||||
logging.info('stock_lot_obj===========:%s' % stock_lot_obj)
|
||||
if not stock_lot_obj:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '未根据RfidCode找到该产品'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
if OldPosition:
|
||||
old_localtion = request.env['sf.shelf.location'].sudo().search(
|
||||
[('barcode', '=', OldPosition)], limit=1)
|
||||
logging.info('old_localtion===========:%s' % old_localtion)
|
||||
new_localtion = request.env['sf.shelf.location'].sudo().search(
|
||||
[('barcode', '=', NewPosition)], limit=1)
|
||||
logging.info('new_localtion===========:%s' % new_localtion)
|
||||
if not new_localtion:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '没有该目标位置'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
if old_localtion:
|
||||
temp_val_sn_id = old_localtion.product_sn_id
|
||||
logging.info('temp_val_sn_id===========:%s' % temp_val_sn_id)
|
||||
old_localtion.product_sn_id = None
|
||||
new_localtion.product_sn_id = temp_val_sn_id
|
||||
logging.info('====1======')
|
||||
else:
|
||||
new_localtion.product_sn_id = stock_lot_obj.id
|
||||
logging.info('=====2======')
|
||||
if ChangeType == 'Part':
|
||||
temp_val_sn_id = None
|
||||
old_localtion = None
|
||||
# if ChangeType == 'Part' or ChangeType == 'Tool':
|
||||
stock_lot_obj = request.env['stock.lot'].sudo().search(
|
||||
[('rfid', '=', RfidCode)], limit=1)
|
||||
logging.info('stock_lot_obj===========:%s' % stock_lot_obj)
|
||||
if not stock_lot_obj:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '未根据RfidCode找到该产品'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
if OldPosition:
|
||||
old_localtion = request.env['sf.shelf.location'].sudo().search(
|
||||
[('barcode', '=', OldPosition)], limit=1)
|
||||
logging.info('old_localtion===========:%s' % old_localtion)
|
||||
new_localtion = request.env['sf.shelf.location'].sudo().search(
|
||||
[('barcode', '=', NewPosition)], limit=1)
|
||||
logging.info('new_localtion===========:%s' % new_localtion)
|
||||
if not new_localtion:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '没有该目标位置'}
|
||||
return json.JSONEncoder().encode(res)
|
||||
if old_localtion:
|
||||
temp_val_sn_id = old_localtion.product_sn_id
|
||||
logging.info('temp_val_sn_id===========:%s' % temp_val_sn_id)
|
||||
old_localtion.product_sn_id = None
|
||||
new_localtion.product_sn_id = temp_val_sn_id
|
||||
logging.info('====1======')
|
||||
else:
|
||||
new_localtion.product_sn_id = stock_lot_obj.id
|
||||
logging.info('=====2======')
|
||||
elif ChangeType == 'Tool':
|
||||
# 对功能刀具库位变更信息进行更改
|
||||
def write_tool(DeciveId):
|
||||
if 'Tool' in DeciveId:
|
||||
shelfinfo = list(filter(lambda x: x.get('DeviceId') == DeciveId,
|
||||
request.env['sf.shelf.location'].sudo().get_sf_shelf_location_info(
|
||||
DeciveId)))
|
||||
total_data = request.env['sf.shelf.location.datasync'].sudo().get_total_data()
|
||||
for item in shelfinfo:
|
||||
logging.info('货架已获取信息:%s' % item)
|
||||
shelf_barcode = request.env['sf.shelf.location.datasync'].sudo().find_our_code(
|
||||
total_data, item['Postion'])
|
||||
location_id = request.env['sf.shelf.location'].sudo().search(
|
||||
[('barcode', '=', shelf_barcode)],
|
||||
limit=1)
|
||||
if location_id:
|
||||
# 如果是线边刀库信息,则对功能刀具移动生成记录
|
||||
if 'Tool' in item['Postion']:
|
||||
tool = request.env['sf.functional.cutting.tool.entity'].sudo().search(
|
||||
[('rfid', '=', item['RfidCode']), ('functional_tool_status', '!=', '已拆除')])
|
||||
tool.sudo().tool_in_out_stock_location(location_id)
|
||||
if tool:
|
||||
location_id.product_sn_id = tool.barcode_id.id
|
||||
# 修改功能刀具状态
|
||||
if item.get('State') == '报警':
|
||||
if tool.functional_tool_status != item.get('State'):
|
||||
tool.write({
|
||||
'functional_tool_status': item['State']
|
||||
})
|
||||
else:
|
||||
location_id.product_sn_id = False
|
||||
if item['RfidCode']:
|
||||
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
|
||||
else:
|
||||
equipment_id = request.env['maintenance.equipment'].sudo().search([('name', '=', DeciveId)])
|
||||
if equipment_id:
|
||||
equipment_id.sudo().register_equipment_tool()
|
||||
else:
|
||||
res_1 = {'Succeed': False, 'ErrorCode': 202, 'Error': f'设备【{DeciveId}】不存在'}
|
||||
return json.JSONEncoder().encode(res_1)
|
||||
|
||||
if OldDeciveId:
|
||||
write_tool(OldDeciveId)
|
||||
elif NewDeciveId:
|
||||
write_tool(NewDeciveId)
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
logging.info('LocationChange error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVToProduct(self, **kw):
|
||||
"""
|
||||
@@ -448,13 +496,16 @@ class Manufacturing_Connect(http.Controller):
|
||||
if 'DeviceId' in ret:
|
||||
logging.info('DeviceId:%s' % ret['DeviceId'])
|
||||
if 'IsComplete' in ret:
|
||||
rfid_codes = []
|
||||
workorder_ids = []
|
||||
if ret['IsComplete'] is True or ret['IsComplete'] is False:
|
||||
for i in range(1, 5):
|
||||
logging.info('F-RfidCode:%s' % i)
|
||||
if f'RfidCode{i}' in ret:
|
||||
rfid_code = ret[f'RfidCode{i}']
|
||||
logging.info('RfidCode:%s' % rfid_code)
|
||||
if rfid_code is not None:
|
||||
if rfid_code is not None and rfid_code != '':
|
||||
rfid_codes.append(rfid_code)
|
||||
domain = [
|
||||
('rfid_code', '=', rfid_code),
|
||||
('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')
|
||||
@@ -462,6 +513,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc')
|
||||
if workorder:
|
||||
for order in workorder:
|
||||
workorder_ids.append(order.id)
|
||||
if order.production_line_state == '待上产线':
|
||||
logging.info(
|
||||
'工单产线状态:%s' % order.production_line_state)
|
||||
@@ -470,27 +522,34 @@ class Manufacturing_Connect(http.Controller):
|
||||
('processing_panel', '=', order.processing_panel)])
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'production_line_state': '已上产线'})
|
||||
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
[
|
||||
('rfid_code', '=', rfid_code), ('type', '=', '上产线'),
|
||||
('production_id', '=', order.production_id.id),
|
||||
('workorder_id', '=', order.id),
|
||||
('workorder_state', '=', 'done')])
|
||||
if workpiece_delivery.status == '待下发':
|
||||
workpiece_delivery.write({'is_manual_work': True})
|
||||
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
# [
|
||||
# ('rfid_code', '=', rfid_code), ('type', '=', '上产线'),
|
||||
# ('production_id', '=', order.production_id.id),
|
||||
# ('workorder_id', '=', order.id),
|
||||
# ('workorder_state', '=', 'done')])
|
||||
# if workpiece_delivery.status == '待下发':
|
||||
# workpiece_delivery.write({'is_manual_work': True})
|
||||
# 下发
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 204,
|
||||
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
|
||||
if ret['IsComplete'] is True:
|
||||
# 向AGV任务调度下发运送空料架任务
|
||||
workorders = request.env['mrp.workorder'].browse(workorder_ids)
|
||||
request.env['sf.agv.scheduling'].add_scheduling(ret['DeviceId'], '运送空料架', workorders)
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 203, 'Error': '未传IsComplete字段'}
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 201, 'Error': '未传DeviceId字段'}
|
||||
except RepeatTaskException as e:
|
||||
logging.info('AGVToProduct error:%s' % e)
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
|
||||
logging.info('AGVToProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVDownProduct(self, **kw):
|
||||
"""
|
||||
@@ -509,7 +568,8 @@ class Manufacturing_Connect(http.Controller):
|
||||
logging.info('ret:%s' % ret)
|
||||
if 'DeviceId' in ret:
|
||||
logging.info('DeviceId:%s' % ret['DeviceId'])
|
||||
delivery_Arr = []
|
||||
# delivery_Arr = []
|
||||
workorder_ids = []
|
||||
if 'IsComplete' in ret:
|
||||
if ret['IsComplete'] is True or ret['IsComplete'] is False:
|
||||
for i in range(1, 5):
|
||||
@@ -517,7 +577,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
if f'RfidCode{i}' in ret:
|
||||
rfid_code = ret[f'RfidCode{i}']
|
||||
logging.info('RfidCode:%s' % rfid_code)
|
||||
if rfid_code is not None:
|
||||
if rfid_code is not None and rfid_code != '':
|
||||
domain = [
|
||||
('rfid_code', '=', rfid_code),
|
||||
('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')
|
||||
@@ -525,6 +585,7 @@ class Manufacturing_Connect(http.Controller):
|
||||
workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc')
|
||||
if workorder:
|
||||
for order in workorder:
|
||||
workorder_ids.append(order.id)
|
||||
if order.production_line_state == '已上产线':
|
||||
logging.info(
|
||||
'工单产线状态:%s' % order.production_line_state)
|
||||
@@ -534,35 +595,41 @@ class Manufacturing_Connect(http.Controller):
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'production_line_state': '已下产线'})
|
||||
workorder.write({'state': 'to be detected'})
|
||||
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
[
|
||||
('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
|
||||
('production_id', '=', order.production_id.id),
|
||||
('workorder_id', '=', order.id),
|
||||
('workorder_state', '=', 'done')])
|
||||
if workpiece_delivery:
|
||||
delivery_Arr.append(workpiece_delivery.id)
|
||||
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
# [
|
||||
# ('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
|
||||
# ('production_id', '=', order.production_id.id),
|
||||
# ('workorder_id', '=', order.id),
|
||||
# ('workorder_state', '=', 'done')])
|
||||
# if workpiece_delivery:
|
||||
# delivery_Arr.append(workpiece_delivery.id)
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 204,
|
||||
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
|
||||
if delivery_Arr:
|
||||
logging.info('delivery_Arr:%s' % delivery_Arr)
|
||||
delivery_workpiece = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
[('id', 'in', delivery_Arr)])
|
||||
if delivery_workpiece:
|
||||
logging.info('开始向agv下发下产线任务')
|
||||
agv_site = request.env['sf.agv.site'].sudo().search([])
|
||||
if agv_site:
|
||||
has_site = agv_site.update_site_state()
|
||||
if has_site is True:
|
||||
is_free = delivery_workpiece._check_avgsite_state()
|
||||
if is_free is True:
|
||||
delivery_workpiece._delivery_avg()
|
||||
logging.info('agv下发下产线任务下发完成')
|
||||
# if delivery_Arr:
|
||||
# logging.info('delivery_Arr:%s' % delivery_Arr)
|
||||
# delivery_workpiece = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
# [('id', 'in', delivery_Arr)])
|
||||
# if delivery_workpiece:
|
||||
# logging.info('开始向agv下发下产线任务')
|
||||
# agv_site = request.env['sf.agv.site'].sudo().search([])
|
||||
# if agv_site:
|
||||
# has_site = agv_site.update_site_state()
|
||||
# if has_site is True:
|
||||
# is_free = delivery_workpiece._check_avgsite_state()
|
||||
# if is_free is True:
|
||||
# delivery_workpiece._delivery_avg()
|
||||
# logging.info('agv下发下产线任务下发完成')
|
||||
if ret['IsComplete'] is True:
|
||||
# 向AGV任务调度下发下产线任务
|
||||
workorders = request.env['mrp.workorder'].browse(workorder_ids)
|
||||
request.env['sf.agv.scheduling'].add_scheduling(ret['DeviceId'], '下产线', workorders)
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 203, 'Error': '未传IsComplete字段'}
|
||||
except RepeatTaskException as e:
|
||||
logging.info('AGVToProduct error:%s' % e)
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@@ -600,3 +667,32 @@ class Manufacturing_Connect(http.Controller):
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
|
||||
cors="*")
|
||||
def AGVStationState(self, **kw):
|
||||
"""
|
||||
中控推送接驳站状态
|
||||
:param kw:
|
||||
:return:
|
||||
"""
|
||||
logging.info('AGVStationState:%s' % kw)
|
||||
try:
|
||||
res = {'Succeed': True}
|
||||
datas = request.httprequest.data
|
||||
ret = json.loads(datas)
|
||||
request.env['center_control.interface.log'].sudo().create(
|
||||
{'content': ret, 'name': 'AutoDeviceApi/AGVStationState'})
|
||||
logging.info('ret:%s' % ret)
|
||||
ret = ret['param']
|
||||
params = {}
|
||||
for i in range(len(ret)):
|
||||
if 'DeviceId' in ret[i] and 'AtHome' in ret[i]:
|
||||
logging.info('DeviceId:%s, AtHome:%s' % (ret[i]['DeviceId'], ret[i]['AtHome']))
|
||||
params[ret[i]['DeviceId']] = '占用' if ret[i]['AtHome'] else '空闲'
|
||||
if params:
|
||||
request.env['sf.agv.site'].update_site_state(params)
|
||||
except Exception as e:
|
||||
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
|
||||
logging.info('AGVDownProduct error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
@@ -25,15 +25,14 @@ class Workpiece(http.Controller):
|
||||
if 'reqCode' in ret:
|
||||
if 'method' in ret:
|
||||
if ret['method'] == 'end':
|
||||
req_codes = ret['reqCode'].split(',')
|
||||
for req_code in req_codes:
|
||||
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
[('name', '=', req_code.strip()), ('task_completion_time', '=', False)])
|
||||
if workpiece_delivery:
|
||||
workpiece_delivery.write({'status': '已配送', 'task_completion_time': datetime.now()})
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 203,
|
||||
'Error': '该reqCode暂未查到对应的工件配送记录'}
|
||||
# 找到对应的AGV调度任务
|
||||
agv_scheduling = request.env['sf.agv.scheduling'].sudo().search(
|
||||
[('name', '=', ret['reqCode']), ('state', '=', '配送中')])
|
||||
if agv_scheduling:
|
||||
agv_scheduling.finish_scheduling()
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 203,
|
||||
'Error': '该reqCode暂未查到对应的AGV任务记录'}
|
||||
else:
|
||||
res = {'Succeed': False, 'ErrorCode': 204, 'Error': '未传method字段'}
|
||||
else:
|
||||
|
||||
16
sf_manufacturing/data/agv_scheduling_data.xml
Normal file
16
sf_manufacturing/data/agv_scheduling_data.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<record id="sequence_agv_scheduling" model="ir.sequence">
|
||||
<field name="name">AGV调度</field>
|
||||
<field name="code">sf.agv.scheduling</field>
|
||||
<field name="prefix">B%(year)s%(month)s%(day)s</field>
|
||||
<field name="padding">4</field>
|
||||
<field name="number_next">1</field>
|
||||
<field name="implementation">standard</field>
|
||||
<field name="use_date_range">True</field>
|
||||
<field name="date_range_period">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -9,3 +9,5 @@ from . import stock
|
||||
from . import res_user
|
||||
from . import production_line_base
|
||||
from . import agv_setting
|
||||
from . import agv_scheduling
|
||||
from . import res_config_setting
|
||||
|
||||
282
sf_manufacturing/models/agv_scheduling.py
Normal file
282
sf_manufacturing/models/agv_scheduling.py
Normal file
@@ -0,0 +1,282 @@
|
||||
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RepeatTaskException(UserError):
|
||||
pass
|
||||
|
||||
|
||||
class AgvScheduling(models.Model):
|
||||
_name = 'sf.agv.scheduling'
|
||||
_description = 'agv调度'
|
||||
_order = 'id desc'
|
||||
|
||||
name = fields.Char('任务单号', index=True, copy=False)
|
||||
|
||||
def _get_agv_route_type_selection(self):
|
||||
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
|
||||
|
||||
agv_route_type = fields.Selection(selection=_get_agv_route_type_selection, string='任务类型', required=True)
|
||||
agv_route_id = fields.Many2one('sf.agv.task.route', '任务路线')
|
||||
start_site_id = fields.Many2one('sf.agv.site', '起点接驳站', required=True)
|
||||
end_site_id = fields.Many2one('sf.agv.site', '终点接驳站', tracking=True)
|
||||
site_state = fields.Selection([
|
||||
('占用', '占用'),
|
||||
('空闲', '空闲')], string='终点接驳站状态', default='占用')
|
||||
state = fields.Selection([
|
||||
('待下发', '待下发'),
|
||||
('配送中', '配送中'),
|
||||
('已配送', '已配送'),
|
||||
('已取消', '已取消')], string='状态', default='待下发', tracking=True)
|
||||
workorder_ids = fields.Many2many('mrp.workorder', 'sf_agv_scheduling_mrp_workorder_ref', string='关联工单')
|
||||
task_create_time = fields.Datetime('任务创建时间')
|
||||
task_delivery_time = fields.Datetime('任务下发时间')
|
||||
task_completion_time = fields.Datetime('任务完成时间')
|
||||
task_duration = fields.Char('任务时长', compute='_compute_task_duration')
|
||||
|
||||
@api.depends('agv_route_type')
|
||||
def _compute_delivery_workpieces(self):
|
||||
for record in self:
|
||||
if record.agv_route_type == '运送空料架':
|
||||
record.delivery_workpieces = '/'
|
||||
else:
|
||||
record.delivery_workpieces = '、'.join(record.workorder_ids.mapped('production_id.name'))
|
||||
|
||||
delivery_workpieces = fields.Char('配送工件', compute=_compute_delivery_workpieces)
|
||||
|
||||
@api.model
|
||||
def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, count_limit=None):
|
||||
domain = domain or []
|
||||
new_domain = []
|
||||
for item in domain:
|
||||
if isinstance(item, list):
|
||||
if item[0] == 'delivery_workpieces':
|
||||
new_domain.append('&')
|
||||
new_domain.append(['workorder_ids.production_id.name', item[1], item[2]])
|
||||
new_domain.append(['agv_route_type', '!=', '运送空料架'])
|
||||
continue
|
||||
new_domain.append(item)
|
||||
|
||||
return super(AgvScheduling, self).web_search_read(new_domain, fields, offset, limit, order, count_limit)
|
||||
|
||||
@api.depends('task_completion_time', 'task_delivery_time')
|
||||
def _compute_task_duration(self):
|
||||
for rec in self:
|
||||
if rec.task_completion_time and rec.task_delivery_time:
|
||||
rec.task_duration = str(rec.task_completion_time - rec.task_delivery_time)
|
||||
else:
|
||||
rec.task_duration = ''
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# We generate a standard reference
|
||||
for vals in vals_list:
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('sf.agv.scheduling') or _('New')
|
||||
return super().create(vals_list)
|
||||
|
||||
def add_scheduling(self, agv_start_site_name, agv_route_type, workorders):
|
||||
""" add_scheduling(agv_start_site_id, agv_route_type, workorders) -> agv_scheduling
|
||||
新增AGV调度
|
||||
params:
|
||||
agv_start_site_name: AGV起点接驳站名称
|
||||
agv_route_type: AGV任务类型
|
||||
workorders: 工单
|
||||
"""
|
||||
_logger.info('创建AGV调度任务\r\n起点为【%s】,任务类型为【%s】,工单为【%s】' % (agv_start_site_name, agv_route_type, workorders))
|
||||
if not workorders:
|
||||
raise UserError(_('工单不能为空'))
|
||||
agv_start_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)], limit=1)
|
||||
if not agv_start_site:
|
||||
raise UserError(_('不存在名称为【%s】的接驳站,请先创建!' % agv_start_site_name))
|
||||
# 如果存在相同任务类型工单的AGV调度任务,则提示错误
|
||||
agv_scheduling = self.sudo().search([
|
||||
('workorder_ids', 'in', workorders.ids),
|
||||
('agv_route_type', '=', agv_route_type),
|
||||
('state', 'in', ['待下发', '配送中'])
|
||||
], limit=1)
|
||||
if agv_scheduling:
|
||||
# 计算agv_scheduling.workorder_ids与workorders的交集
|
||||
repetitive_workorders = agv_scheduling.workorder_ids & workorders
|
||||
raise RepeatTaskException(
|
||||
'制造订单号【%s】已存在于【%s】AGV调度任务,请勿重复下发!' %
|
||||
(','.join(repetitive_workorders.mapped('production_id.name')), agv_scheduling.name)
|
||||
)
|
||||
|
||||
vals = {
|
||||
'start_site_id': agv_start_site.id,
|
||||
'agv_route_type': agv_route_type,
|
||||
'workorder_ids': workorders.ids,
|
||||
# 'workpiece_delivery_ids': deliveries.mapped('id') if deliveries else [],
|
||||
'task_create_time': fields.Datetime.now()
|
||||
}
|
||||
# 如果只有唯一任务路线,则自动赋予终点接驳站跟任务名称
|
||||
agv_routes = self.env['sf.agv.task.route'].sudo().search([
|
||||
('route_type', '=', agv_route_type),
|
||||
('start_site_id', '=', agv_start_site.id)
|
||||
])
|
||||
if not agv_routes:
|
||||
raise UserError(_('不存在起点为【%s】的【%s】任务路线,请先创建!' % (agv_start_site_name, agv_route_type)))
|
||||
idle_route = None
|
||||
if len(agv_routes) == 1:
|
||||
idle_route = agv_routes[0]
|
||||
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id})
|
||||
else:
|
||||
# 判断终点接驳站是否为空闲
|
||||
idle_routes = agv_routes.filtered(lambda r: r.end_site_id.state == '空闲')
|
||||
if idle_routes:
|
||||
# 将空闲的路线按照终点接驳站名称排序
|
||||
idle_routes = sorted(idle_routes, key=lambda r: r.end_site_id.name)
|
||||
idle_route = idle_routes[0]
|
||||
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id})
|
||||
try:
|
||||
scheduling = self.env['sf.agv.scheduling'].sudo().create(vals)
|
||||
# 触发空闲接驳站状态更新,触发新任务下发
|
||||
if idle_route and idle_route.end_site_id.state == '空闲':
|
||||
scheduling.dispatch_scheduling(idle_route)
|
||||
|
||||
except Exception as e:
|
||||
_logger.error('添加AGV调度任务失败: %s', e)
|
||||
raise UserError(_('添加AGV调度任务失败: %s', e))
|
||||
|
||||
return scheduling
|
||||
|
||||
def on_site_state_change(self, agv_site_id, agv_site_state):
|
||||
"""
|
||||
响应AGV接驳站站点状态变化
|
||||
params:
|
||||
agv_site_id: 接驳站ID
|
||||
agv_site_state: 站点状态('空闲', '占用')
|
||||
"""
|
||||
if agv_site_state == '空闲':
|
||||
# 查询终点接驳站为agv_site_id的AGV路线
|
||||
task_routes = self.env['sf.agv.task.route'].sudo().search([('end_site_id', '=', agv_site_id)])
|
||||
agv_scheduling = self.env['sf.agv.scheduling'].sudo().search(
|
||||
[('state', '=', '待下发'), ('agv_route_type', 'in', task_routes.mapped('route_type'))],
|
||||
order='id asc',
|
||||
limit=1
|
||||
)
|
||||
task_route = task_routes.filtered(
|
||||
lambda r: r.start_site_id == agv_scheduling.start_site_id and r.start_site_id == agv_scheduling.start_site_id
|
||||
)
|
||||
if task_route:
|
||||
# 下发AGV调度任务并修改接驳站状态为占用
|
||||
agv_scheduling.dispatch_scheduling(task_route)
|
||||
|
||||
def _delivery_avg(self):
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
position_code_arr = [{
|
||||
'positionCode': self.start_site_id.name,
|
||||
'code': '00'
|
||||
}, {
|
||||
'positionCode': self.end_site_id.name,
|
||||
'code': '00'
|
||||
}]
|
||||
res = {'reqCode': self.name, 'reqTime': '', 'clientCode': '', 'tokenCode': '',
|
||||
'taskTyp': 'F01', 'ctnrTyp': '', 'ctnrCode': '', 'wbCode': config['wbcode'],
|
||||
'positionCodePath': position_code_arr,
|
||||
'podCode': '',
|
||||
'podDir': '', 'materialLot': '', 'priority': '', 'taskCode': '', 'agvCode': '', 'materialLot': '',
|
||||
'data': ''}
|
||||
try:
|
||||
logging.info('AGV请求路径:%s' % config['agv_rcs_url'])
|
||||
logging.info('AGV-json:%s' % res)
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
ret = requests.post((config['agv_rcs_url']), json=res, headers=headers)
|
||||
ret = ret.json()
|
||||
logging.info('config-ret:%s' % ret)
|
||||
if ret['code'] == 0:
|
||||
return True
|
||||
else:
|
||||
raise UserError(ret['message'])
|
||||
except Exception as e:
|
||||
logging.info('config-e:%s' % e)
|
||||
raise UserError("工件配送请求agv失败:%s" % e)
|
||||
|
||||
def button_cancel(self):
|
||||
# 弹出二次确认窗口后执行
|
||||
for rec in self:
|
||||
if rec.state != '待下发':
|
||||
raise UserError('只有待下发状态的AGV调度任务才能取消!')
|
||||
rec.state = '已取消'
|
||||
|
||||
def finish_scheduling(self):
|
||||
"""
|
||||
完成调度任务
|
||||
"""
|
||||
for rec in self:
|
||||
if rec.state != '配送中':
|
||||
return False
|
||||
_logger.info('AGV任务调度:完成任务%s' % rec)
|
||||
rec.state = '已配送'
|
||||
rec.task_completion_time = fields.Datetime.now()
|
||||
|
||||
def dispatch_scheduling(self, agv_task_route):
|
||||
"""
|
||||
下发调度任务
|
||||
params:
|
||||
agv_route sf.agv.task.route对象
|
||||
"""
|
||||
for rec in self:
|
||||
if rec.state != '待下发':
|
||||
return False
|
||||
_logger.info('AGV任务调度:下发调度任务,路线为%s' % agv_task_route)
|
||||
rec.state = '配送中'
|
||||
rec.task_delivery_time = fields.Datetime.now()
|
||||
rec.site_state = '空闲'
|
||||
rec.end_site_id = agv_task_route.end_site_id.id
|
||||
rec.agv_route_id = agv_task_route.id
|
||||
is_agv_task_dispatch = self.env['ir.config_parameter'].sudo().get_param('is_agv_task_dispatch')
|
||||
if is_agv_task_dispatch:
|
||||
rec._delivery_avg()
|
||||
# 更新接驳站状态
|
||||
rec.env['sf.agv.site'].update_site_state({rec.end_site_id.name: '占用'}, False)
|
||||
|
||||
def write(self, vals):
|
||||
if vals.get('state', False):
|
||||
if vals['state'] == '已取消':
|
||||
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({'status': '待下发'})
|
||||
elif vals['state'] == '已配送':
|
||||
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({
|
||||
'status': '已配送',
|
||||
'feeder_station_destination_id': self.end_site_id.id,
|
||||
'route_id': self.agv_route_id.id,
|
||||
'task_completion_time': fields.Datetime.now()
|
||||
})
|
||||
elif vals['state'] == '配送中':
|
||||
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({
|
||||
'feeder_station_destination_id': self.end_site_id.id,
|
||||
'route_id': self.agv_route_id.id,
|
||||
'task_delivery_time': fields.Datetime.now()
|
||||
})
|
||||
return super().write(vals)
|
||||
|
||||
|
||||
class ResMrpWorkOrder(models.Model):
|
||||
_inherit = 'mrp.workorder'
|
||||
|
||||
agv_scheduling_ids = fields.Many2many(
|
||||
'sf.agv.scheduling',
|
||||
'sf_agv_scheduling_mrp_workorder_ref',
|
||||
string='AGV调度',
|
||||
domain=[('state', '!=', '已取消')])
|
||||
|
||||
def get_down_product_agv_scheduling(self):
|
||||
"""
|
||||
获取关联的制造订单下产线的agv任务
|
||||
"""
|
||||
self.ensure_one()
|
||||
workorder_ids = self.production_id.workorder_ids
|
||||
cnc_workorder = workorder_ids.filtered(
|
||||
lambda w: w.routing_type == 'CNC加工' and w.state == 'done' and w.processing_panel == self.processing_panel
|
||||
)
|
||||
if cnc_workorder:
|
||||
agv_schedulingss = cnc_workorder.agv_scheduling_ids
|
||||
return agv_schedulingss.filtered(lambda a: a.state == '已配送' and a.agv_route_type == '下产线')
|
||||
else:
|
||||
return None
|
||||
@@ -5,50 +5,77 @@ import time
|
||||
from odoo import fields, models, api
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgvSetting(models.Model):
|
||||
_name = 'sf.agv.site'
|
||||
_description = 'agv站点'
|
||||
|
||||
name = fields.Char('位置编号')
|
||||
owning_region = fields.Char('所属区域')
|
||||
# owning_region = fields.Char('所属区域')
|
||||
state = fields.Selection([
|
||||
('占用', '占用'),
|
||||
('空闲', '空闲')], string='状态')
|
||||
divide_the_work = fields.Char('主要分工')
|
||||
active = fields.Boolean('有效', default=True)
|
||||
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True,
|
||||
domain=[('is_agv_scheduling', '=', True)])
|
||||
|
||||
def update_site_state(self):
|
||||
# 调取中控的接驳站接口并修改对应站点的状态
|
||||
config = self.env['res.config.settings'].get_values()
|
||||
# token = sf_sync_config['token'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A]
|
||||
headers = {'Authorization': config['center_control_Authorization']}
|
||||
center_control_url = config['center_control_url'] + "/AutoDeviceApi/GetAgvStationState?date="
|
||||
timestamp = int(time.time())
|
||||
center_control_url += str(timestamp)
|
||||
logging.info('工件配送-请求中控地址:%s' % center_control_url)
|
||||
try:
|
||||
center_control_r = requests.get(center_control_url, headers=headers, timeout=10) # 设置超时为60秒
|
||||
ret = center_control_r.json()
|
||||
logging.info('工件配送-请求中控站点信息:%s' % ret)
|
||||
self.env['center_control.interface.log'].sudo().create(
|
||||
{'content': ret, 'name': 'AutoDeviceApi/GetAgvStationState?date=%s' % str(timestamp)})
|
||||
if ret['Succeed'] is True:
|
||||
datas = ret['Datas']
|
||||
for item in self:
|
||||
for da in datas:
|
||||
if da['DeviceId'] == item.name:
|
||||
if da['AtHome'] is True:
|
||||
item.state = '占用'
|
||||
else:
|
||||
item.state = '空闲'
|
||||
return True
|
||||
except requests.exceptions.Timeout:
|
||||
logging.error('工件配送-请求中控接口超时')
|
||||
return False
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error('工件配送-请求中控接口错误: %s', e)
|
||||
return False
|
||||
# name必须唯一
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', '站点编号必须唯一!'),
|
||||
]
|
||||
|
||||
# def update_site_state(self):
|
||||
# # 调取中控的接驳站接口并修改对应站点的状态
|
||||
# config = self.env['res.config.settings'].get_values()
|
||||
# # token = sf_sync_config['token'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A]
|
||||
# headers = {'Authorization': config['center_control_Authorization']}
|
||||
# center_control_url = config['center_control_url'] + "/AutoDeviceApi/GetAgvStationState?date="
|
||||
# timestamp = int(time.time())
|
||||
# center_control_url += str(timestamp)
|
||||
# logging.info('工件配送-请求中控地址:%s' % center_control_url)
|
||||
# try:
|
||||
# center_control_r = requests.get(center_control_url, headers=headers, timeout=10) # 设置超时为60秒
|
||||
# ret = center_control_r.json()
|
||||
# logging.info('工件配送-请求中控站点信息:%s' % ret)
|
||||
# self.env['center_control.interface.log'].sudo().create(
|
||||
# {'content': ret, 'name': 'AutoDeviceApi/GetAgvStationState?date=%s' % str(timestamp)})
|
||||
# if ret['Succeed'] is True:
|
||||
# datas = ret['Datas']
|
||||
# for item in self:
|
||||
# for da in datas:
|
||||
# if da['DeviceId'] == item.name:
|
||||
# if da['AtHome'] is True:
|
||||
# item.state = '占用'
|
||||
# else:
|
||||
# item.state = '空闲'
|
||||
# return True
|
||||
# except requests.exceptions.Timeout:
|
||||
# logging.error('工件配送-请求中控接口超时')
|
||||
# return False
|
||||
# except requests.exceptions.RequestException as e:
|
||||
# logging.error('工件配送-请求中控接口错误: %s', e)
|
||||
# return False
|
||||
|
||||
def update_site_state(self, agv_site_state_arr, notify=True):
|
||||
"""
|
||||
更新接驳站状态
|
||||
params:
|
||||
agv_site_state_arr: {'A01': '空闲', 'B01': '占用'}
|
||||
notify: 是否通知调度(非中控发起的状态改变不触发调度任务)
|
||||
"""
|
||||
if isinstance(agv_site_state_arr, dict):
|
||||
for agv_site_name, is_occupy in agv_site_state_arr.items():
|
||||
agv_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_site_name)])
|
||||
if agv_site:
|
||||
agv_site.state = is_occupy
|
||||
if notify:
|
||||
self.env['sf.agv.scheduling'].on_site_state_change(agv_site.id, agv_site.state)
|
||||
else:
|
||||
_logger.error("更新失败:接驳站站点错误!%s" % agv_site_name)
|
||||
raise UserError("更新失败:接驳站站点错误!")
|
||||
|
||||
|
||||
class AgvTaskRoute(models.Model):
|
||||
@@ -71,6 +98,17 @@ class AgvTaskRoute(models.Model):
|
||||
if self.end_site_id == self.start_site_id:
|
||||
raise UserError("您选择的终点接驳站与起点接驳站重复,请重新选择")
|
||||
|
||||
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', domain=[('is_agv_scheduling', '=', True)],
|
||||
compute="_compute_region")
|
||||
|
||||
@api.depends('end_site_id')
|
||||
def _compute_region(self):
|
||||
for record in self:
|
||||
if record.end_site_id:
|
||||
record.workcenter_id = record.end_site_id.workcenter_id
|
||||
else:
|
||||
record.workcenter_id = None
|
||||
|
||||
|
||||
class Center_controlInterfaceLog(models.Model):
|
||||
_name = 'center_control.interface.log'
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import base64
|
||||
import datetime
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from itertools import groupby
|
||||
from odoo import api, fields, models, _
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.addons.sf_base.commons.common import Common
|
||||
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
|
||||
@@ -77,9 +79,10 @@ class MrpProduction(models.Model):
|
||||
('pending_cam', '待加工'),
|
||||
('progress', '加工中'),
|
||||
('rework', '返工'),
|
||||
('scrap', '报废'),
|
||||
('to_close', 'To Close'),
|
||||
('done', 'Done'),
|
||||
('cancel', '报废')], string='State',
|
||||
('cancel', '已取消')], string='State',
|
||||
compute='_compute_state', copy=False, index=True, readonly=True,
|
||||
store=True, tracking=True,
|
||||
help=" * Draft: The MO is not confirmed yet.\n"
|
||||
@@ -120,8 +123,39 @@ class MrpProduction(models.Model):
|
||||
# 上传零件图纸
|
||||
part_drawing = fields.Binary('零件图纸')
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
@api.depends('product_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
for item in self:
|
||||
item.manual_quotation = item.product_id.manual_quotation
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True)
|
||||
is_scrap = fields.Boolean('是否报废', default=False)
|
||||
is_remanufacture = fields.Boolean('是否重新制造', default=False)
|
||||
remanufacture_count = fields.Integer("重新制造订单数量", compute='_compute_remanufacture_production_ids')
|
||||
remanufacture_production_id = fields.Many2one('mrp.production', string='')
|
||||
|
||||
@api.depends('remanufacture_production_id')
|
||||
def _compute_remanufacture_production_ids(self):
|
||||
for production in self:
|
||||
if production.remanufacture_production_id:
|
||||
remanufacture_production = self.env['mrp.production'].search(
|
||||
[('id', '=', production.remanufacture_production_id.id)])
|
||||
if remanufacture_production:
|
||||
production.remanufacture_count = len(remanufacture_production)
|
||||
else:
|
||||
production.remanufacture_count = 0
|
||||
|
||||
def action_view_remanufacture_productions(self):
|
||||
self.ensure_one()
|
||||
mrp_production = self.env['mrp.production'].search(
|
||||
[('id', '=', self.remanufacture_production_id.id)])
|
||||
action = {
|
||||
'res_model': 'mrp.production',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_id': mrp_production.id,
|
||||
}
|
||||
return action
|
||||
|
||||
@api.depends(
|
||||
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state',
|
||||
@@ -166,7 +200,7 @@ class MrpProduction(models.Model):
|
||||
production.state = 'pending_cam'
|
||||
|
||||
if production.state == 'progress':
|
||||
if all(wo_state not in ('progress', 'done', 'rework') for wo_state in
|
||||
if all(wo_state not in ('progress', 'done', 'rework', 'scrap') for wo_state in
|
||||
production.workorder_ids.mapped('state')):
|
||||
production.state = 'pending_cam'
|
||||
if production.is_rework is True:
|
||||
@@ -184,6 +218,11 @@ class MrpProduction(models.Model):
|
||||
for wo in
|
||||
production.workorder_ids):
|
||||
production.state = 'rework'
|
||||
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
|
||||
production.state = 'scrap'
|
||||
if any(dr.test_results == '报废' and dr.handle_result == '已处理' for dr in
|
||||
production.detection_result_ids):
|
||||
production.state = 'cancel'
|
||||
# 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工
|
||||
if production.tool_state == '2':
|
||||
production.state = 'rework'
|
||||
@@ -279,8 +318,10 @@ class MrpProduction(models.Model):
|
||||
# cnc程序获取
|
||||
def fetchCNC(self, production_names):
|
||||
cnc = self.env['mrp.production'].search([('id', '=', self.id)])
|
||||
quick_order = self.env['quick.easy.order'].search(
|
||||
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
|
||||
quick_order = False
|
||||
if cnc.product_id.default_code:
|
||||
quick_order = self.env['quick.easy.order'].search(
|
||||
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
|
||||
programme_way = False
|
||||
if cnc.manual_quotation is True:
|
||||
programme_way = 'manual operation'
|
||||
@@ -437,44 +478,45 @@ class MrpProduction(models.Model):
|
||||
# self.env['mrp.workorder'].json_workorder_str(k, production, route))
|
||||
# 表面工艺工序
|
||||
# 获取表面工艺id
|
||||
if production.product_id.model_process_parameters_ids:
|
||||
logging.info('model_process_parameters_ids:%s' % production.product_id.model_process_parameters_ids)
|
||||
surface_technics_arr = []
|
||||
# 工序id
|
||||
route_workcenter_arr = []
|
||||
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
|
||||
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
|
||||
route_workcenter_arr.append(item.route_workcenter_id.id)
|
||||
if surface_technics_arr:
|
||||
production_process_category = self.env['sf.production.process.category'].search(
|
||||
[('production_process_ids.id', 'in', surface_technics_arr)],
|
||||
order='sequence asc'
|
||||
)
|
||||
# 用filter刷选表面工艺id'是否存在工艺类别对象里
|
||||
if production_process_category:
|
||||
for p in production_process_category:
|
||||
logging.info('production_process_category:%s' % p.name)
|
||||
production_process = p.production_process_ids.filtered(
|
||||
lambda pp: pp.id in surface_technics_arr)
|
||||
if production_process:
|
||||
process_parameter = production.product_id.model_process_parameters_ids.filtered(
|
||||
lambda pm: pm.process_id.id == production_process.id)
|
||||
if process_parameter:
|
||||
# 产品为表面工艺服务的供应商
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', process_parameter.id)])
|
||||
if product_production_process:
|
||||
route_production_process = self.env[
|
||||
'mrp.routing.workcenter'].search(
|
||||
[('surface_technics_id', '=', production_process.id),
|
||||
('id', 'in', route_workcenter_arr)])
|
||||
if route_production_process:
|
||||
workorders_values.append(
|
||||
self.env[
|
||||
'mrp.workorder']._json_workorder_surface_process_str(
|
||||
production, route_production_process,
|
||||
process_parameter,
|
||||
product_production_process.seller_ids[0].partner_id.id))
|
||||
# 工序id
|
||||
surface_technics_arr = []
|
||||
route_workcenter_arr = []
|
||||
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
|
||||
if item.route_workcenter_id.surface_technics_id.id:
|
||||
for process_param in production.product_id.model_process_parameters_ids:
|
||||
logging.info('process_param:%s%s' % (process_param.id, process_param.name))
|
||||
if item.route_workcenter_id.surface_technics_id == process_param.process_id:
|
||||
logging.info(
|
||||
'surface_technics_id:%s%s' % (item.route_workcenter_id.surface_technics_id.id,
|
||||
item.route_workcenter_id.surface_technics_id.name))
|
||||
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
|
||||
route_workcenter_arr.append(item.route_workcenter_id.id)
|
||||
if surface_technics_arr:
|
||||
production_process = self.env['sf.production.process'].search(
|
||||
[('id', 'in', surface_technics_arr)],
|
||||
order='sequence asc'
|
||||
)
|
||||
for p in production_process:
|
||||
logging.info('production_process:%s' % p.name)
|
||||
# if production_process:
|
||||
process_parameter = production.product_id.model_process_parameters_ids.filtered(
|
||||
lambda pm: pm.process_id.id == p.id)
|
||||
if process_parameter:
|
||||
# 产品为表面工艺服务的供应商
|
||||
product_production_process = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', process_parameter.id)])
|
||||
if product_production_process:
|
||||
route_production_process = self.env[
|
||||
'mrp.routing.workcenter'].search(
|
||||
[('surface_technics_id', '=', p.id),
|
||||
('id', 'in', route_workcenter_arr)])
|
||||
if route_production_process:
|
||||
workorders_values.append(
|
||||
self.env[
|
||||
'mrp.workorder']._json_workorder_surface_process_str(
|
||||
production, route_production_process,
|
||||
process_parameter,
|
||||
product_production_process.seller_ids[0].partner_id.id))
|
||||
elif production.product_id.categ_id.type == '坯料':
|
||||
embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
|
||||
[('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)],
|
||||
@@ -624,7 +666,7 @@ class MrpProduction(models.Model):
|
||||
# 表面工艺工序
|
||||
# 模型类型的表面工艺工序模版
|
||||
surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids
|
||||
# 产品选择的表面工艺
|
||||
# 产品选择的表面工艺参数
|
||||
model_process_parameters_ids = rec.product_id.model_process_parameters_ids
|
||||
process_dict = {}
|
||||
if model_process_parameters_ids:
|
||||
@@ -633,7 +675,7 @@ class MrpProduction(models.Model):
|
||||
for surface_tmpl_id in surface_tmpl_ids:
|
||||
if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id:
|
||||
surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name
|
||||
process_dict.update({int(process_id.category_id.code): '%s-%s' % (
|
||||
process_dict.update({int(process_id.sequence): '%s-%s' % (
|
||||
surface_tmpl_name, process_parameters_id.name)})
|
||||
process_list = sorted(process_dict.keys())
|
||||
for process_num in process_list:
|
||||
@@ -650,14 +692,16 @@ class MrpProduction(models.Model):
|
||||
raise ValidationError('该产品【加工面板】为空!')
|
||||
else:
|
||||
raise ValidationError('该产品没有选择【模版类型】!')
|
||||
|
||||
logging.info('sequence_list: %s' % sequence_list)
|
||||
for work in rec.workorder_ids:
|
||||
if sequence_list.get(work.name):
|
||||
work.sequence = sequence_list[work.name]
|
||||
work_name = work.name
|
||||
logging.info(work_name)
|
||||
if sequence_list.get(work_name):
|
||||
work.sequence = sequence_list[work_name]
|
||||
elif sequence_list.get(work.processing_panel):
|
||||
processing_panel = sequence_list.get(work.processing_panel)
|
||||
if processing_panel.get(work.name):
|
||||
work.sequence = processing_panel[work.name]
|
||||
if processing_panel.get(work_name):
|
||||
work.sequence = processing_panel[work_name]
|
||||
else:
|
||||
raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name)
|
||||
else:
|
||||
@@ -683,8 +727,9 @@ class MrpProduction(models.Model):
|
||||
sequence_max += 1
|
||||
panel_sequence_list.update({tmpl_id.route_workcenter_id.name: sequence_max})
|
||||
for work_id in work_ids:
|
||||
if panel_sequence_list.get(work_id.name):
|
||||
work_id.sequence = panel_sequence_list[work_id.name]
|
||||
work_name = work_id.name
|
||||
if panel_sequence_list.get(work_name):
|
||||
work_id.sequence = panel_sequence_list[work_name]
|
||||
|
||||
# 创建工单并进行排序
|
||||
def _create_workorder(self, item):
|
||||
@@ -692,6 +737,53 @@ class MrpProduction(models.Model):
|
||||
self._reset_work_order_sequence()
|
||||
return True
|
||||
|
||||
def process_range_time(self):
|
||||
for production in self:
|
||||
works = production.workorder_ids
|
||||
pro_plan = self.env['sf.production.plan'].search([('production_id', '=', production.id)], limit=1)
|
||||
if not pro_plan:
|
||||
continue
|
||||
type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False}
|
||||
# 最后一次加工结束时间
|
||||
last_time = pro_plan.date_planned_start
|
||||
# 预置时间
|
||||
for work in works:
|
||||
count = type_map.get(work.routing_type)
|
||||
date_planned_end = None
|
||||
date_planned_start = None
|
||||
duration_expected = datetime.timedelta(minutes=work.duration_expected)
|
||||
reserve_time = datetime.timedelta(minutes=work.reserved_duration)
|
||||
if not count:
|
||||
# 第一轮加工
|
||||
if work.routing_type == '装夹预调':
|
||||
date_planned_end = last_time - reserve_time
|
||||
date_planned_start = date_planned_end - duration_expected
|
||||
elif work.routing_type == 'CNC加工':
|
||||
date_planned_start = last_time
|
||||
date_planned_end = last_time + duration_expected
|
||||
last_time = date_planned_end
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
type_map.update({work.routing_type: True})
|
||||
else:
|
||||
date_planned_start = last_time + reserve_time
|
||||
date_planned_end = date_planned_start + duration_expected
|
||||
last_time = date_planned_end
|
||||
work.leave_id.write({
|
||||
'date_from': date_planned_start,
|
||||
'date_to': date_planned_end,
|
||||
})
|
||||
# work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end})
|
||||
work.date_planned_start = date_planned_start
|
||||
work.date_planned_finished = date_planned_end
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', work.routing_type)])
|
||||
|
||||
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,
|
||||
'duration_expected': routing_workcenter.time_cycle})
|
||||
|
||||
# 修改标记已完成方法
|
||||
def button_mark_done1(self):
|
||||
if not self.workorder_ids.filtered(lambda w: w.routing_type not in ['表面工艺']):
|
||||
@@ -714,6 +806,10 @@ class MrpProduction(models.Model):
|
||||
backorders = backorders - productions_to_backorder
|
||||
|
||||
productions_not_to_backorder._post_inventory(cancel_backorder=True)
|
||||
# if self.workorder_ids.filtered(lambda w: w.routing_type in ['表面工艺']):
|
||||
# move_finish = self.env['stock.move'].search([('created_production_id', '=', self.id)])
|
||||
# if move_finish:
|
||||
# move_finish._action_assign()
|
||||
productions_to_backorder._post_inventory(cancel_backorder=True)
|
||||
|
||||
# if completed products make other confirmed/partially_available moves available, assign them
|
||||
@@ -788,6 +884,23 @@ class MrpProduction(models.Model):
|
||||
})
|
||||
return action
|
||||
|
||||
# 报废
|
||||
def button_scrap_new(self):
|
||||
cloud_programming = self._cron_get_programming_state()
|
||||
return {
|
||||
'name': _('报废'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.production.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_production_id': self.id,
|
||||
'default_reprogramming_num': cloud_programming['reprogramming_num'],
|
||||
'default_programming_states': cloud_programming['programming_state'],
|
||||
'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False
|
||||
}
|
||||
}
|
||||
|
||||
# 返工
|
||||
def button_rework(self):
|
||||
cloud_programming = None
|
||||
@@ -905,7 +1018,6 @@ class MrpProduction(models.Model):
|
||||
# production.write(
|
||||
# {'state': 'progress', 'programming_state': '已编程', 'is_rework': False})
|
||||
# logging.info('返工含有已编程未下发的程序更新完成:%s' % production.name)
|
||||
logging.info('更新程序完成:%s' % production.name)
|
||||
|
||||
else:
|
||||
raise UserError(result['message'])
|
||||
@@ -913,116 +1025,108 @@ class MrpProduction(models.Model):
|
||||
logging.info('get_new_program error:%s' % e)
|
||||
raise UserError("从云平台获取最新程序失败,请联系管理员")
|
||||
|
||||
def recreateManufacturing(self):
|
||||
def recreateManufacturing(self, item):
|
||||
"""
|
||||
重新生成制造订单
|
||||
"""
|
||||
if self.is_scrap is True:
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', productions.origin)])
|
||||
values = self.env['mrp.production'].create_production1_values(self.production_id)
|
||||
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
|
||||
self.production_id.company_id).create(
|
||||
values)
|
||||
# self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
|
||||
self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
|
||||
productions._create_workorder()
|
||||
productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
|
||||
(
|
||||
p.move_dest_ids.procure_method != 'make_to_order' and
|
||||
not p.move_raw_ids and not p.workorder_ids)).action_confirm()
|
||||
for production_item in productions:
|
||||
process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if process_parameter_workorder:
|
||||
is_pick = False
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
|
||||
for i in range(len(sorted_workorders) - 1):
|
||||
if m == 0:
|
||||
is_pick = False
|
||||
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
|
||||
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
|
||||
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
|
||||
if sorted_workorders[i] not in consecutive_workorders:
|
||||
consecutive_workorders.append(sorted_workorders[i])
|
||||
consecutive_workorders.append(sorted_workorders[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
|
||||
if is_pick is False:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
|
||||
production_item)
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
|
||||
production_item)
|
||||
if sorted_workorders[i] in consecutive_workorders:
|
||||
is_pick = True
|
||||
consecutive_workorders = []
|
||||
m = 0
|
||||
if m == len(consecutive_workorders) - 1 and m != 0:
|
||||
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production_item)
|
||||
if is_pick is False and m == 0:
|
||||
if len(sorted_workorders) == 1:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item)
|
||||
else:
|
||||
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production_item)
|
||||
|
||||
for production in productions:
|
||||
origin_production = production.move_dest_ids and production.move_dest_ids[
|
||||
0].raw_material_production_id or False
|
||||
orderpoint = production.orderpoint_id
|
||||
if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
|
||||
production.message_post(
|
||||
body=_('This production order has been created from Replenishment Report.'),
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_note')
|
||||
elif orderpoint:
|
||||
production.message_post_with_view(
|
||||
'mail.message_origin_link',
|
||||
values={'self': production, 'origin': orderpoint},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
elif origin_production:
|
||||
production.message_post_with_view(
|
||||
'mail.message_origin_link',
|
||||
values={'self': production, 'origin': origin_production},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
|
||||
'''
|
||||
创建生产计划
|
||||
'''
|
||||
# 工单耗时
|
||||
workorder_duration = 0
|
||||
for workorder in productions.workorder_ids:
|
||||
workorder_duration += workorder.duration_expected
|
||||
|
||||
if sale_order:
|
||||
sale_order.mrp_production_ids |= productions
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(self.production_id.company_id).create({
|
||||
'name': productions.name,
|
||||
'order_deadline': sale_order.deadline_of_delivery,
|
||||
'production_id': productions.id,
|
||||
'date_planned_start': productions.date_planned_start,
|
||||
'origin': productions.origin,
|
||||
'product_qty': productions.product_qty,
|
||||
'product_id': productions.product_id.id,
|
||||
'state': 'draft',
|
||||
})
|
||||
procurement_requests = []
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', self.origin)])
|
||||
values = self.env['mrp.production'].create_production1_values(self)
|
||||
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
|
||||
# self.company_id).create(
|
||||
# values)
|
||||
# 查询出库移动记录
|
||||
out_picking = self.env['stock.picking'].search(
|
||||
[('origin', '=', sale_order.name), ('name', 'ilike', 'WH/OUT/')])
|
||||
move = out_picking.move_ids.filtered(lambda pd: pd.product_id == self.product_id)
|
||||
move_values = {'product_description_variants': '',
|
||||
'date_planned': fields.Datetime.now(),
|
||||
'date_deadline': fields.Datetime.now(),
|
||||
'move_dest_ids': move,
|
||||
'group_id': move.group_id,
|
||||
'route_ids': [],
|
||||
'warehouse_id': self.warehouse_id,
|
||||
'priority': 0,
|
||||
'orderpoint_id': False,
|
||||
'product_packaging_id': False}
|
||||
procurement_requests.append(self.env['procurement.group'].Procurement(
|
||||
move.product_id, 1.0, move.product_uom,
|
||||
move.location_id, move.rule_id and move.rule_id.name or "/",
|
||||
sale_order.name, move.company_id, move_values))
|
||||
self.env['procurement.group'].run(procurement_requests,
|
||||
raise_user_error=not self.env.context.get('from_orderpoint'))
|
||||
productions = self.env['mrp.production'].sudo().search(
|
||||
[('origin', '=', self.origin)], order='id desc', limit=1)
|
||||
move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc')
|
||||
for mo in move:
|
||||
if mo.procure_method == 'make_to_order' and mo.name != productions.name:
|
||||
if mo.name == '/':
|
||||
domain = [('barcode', '=', 'WH-PC'), ('sequence_code', '=', 'PC')]
|
||||
elif mo.name == '拉':
|
||||
domain = [('barcode', '=', 'WH-INTERNAL'), ('sequence_code', '=', 'INT')]
|
||||
picking_type = self.env['stock.picking.type'].search(domain)
|
||||
mo.write({'picking_type_id': picking_type.id})
|
||||
mo._assign_picking()
|
||||
else:
|
||||
if mo.reference != productions.name:
|
||||
mo.reference = productions.name
|
||||
if mo.production_id:
|
||||
if mo.production_id != productions:
|
||||
mo.production_id = False
|
||||
mo_move = self.env['stock.move'].search(
|
||||
[('origin', '=', sale_order.name), ('reference', 'ilike', 'WH/MO/')])
|
||||
if mo_move:
|
||||
sfp_move = self.env['stock.move'].search(
|
||||
[('origin', '=', sale_order.name), ('reference', 'ilike', 'WH/SFP/')], limit=1)
|
||||
mo_move.write({'reference': sfp_move.reference, 'partner_id': sfp_move.partner_id.id,
|
||||
'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id,
|
||||
'production_id': False})
|
||||
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
|
||||
# productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
|
||||
# {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
|
||||
stock_picking_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)])
|
||||
for pick in stock_picking_remanufacture:
|
||||
if pick.name.startswith('WH/PC/') or pick.name.startswith('WH/INT/'):
|
||||
if pick.move_ids:
|
||||
product_type_id = pick.move_ids[0].product_id.categ_id
|
||||
if product_type_id.name == '坯料':
|
||||
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
|
||||
if not location_id:
|
||||
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
|
||||
break
|
||||
if pick.picking_type_id.name == '内部调拨':
|
||||
if pick.location_dest_id.product_type != product_type_id:
|
||||
pick.location_dest_id = location_id.id
|
||||
elif pick.picking_type_id.name == '生产发料':
|
||||
if pick.location_id.product_type != product_type_id:
|
||||
pick.location_id = location_id.id
|
||||
scarp_process_parameter_workorder = self.env['mrp.workorder'].search(
|
||||
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', self.id),
|
||||
('is_subcontract', '=', True)])
|
||||
if scarp_process_parameter_workorder:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.programming_no), ('id', '!=', productions.id)], order='name asc')
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search([('origin', 'ilike', ','.join(production_list))])
|
||||
for purchase_item in purchase_orders.order_line:
|
||||
for process_item in scarp_process_parameter_workorder:
|
||||
if purchase_item.product_id.categ_type == '表面工艺':
|
||||
if purchase_item.product_id.server_product_process_parameters_id == process_item.surface_technics_parameters_id:
|
||||
if purchase_orders.origin.find(productions.name) == -1:
|
||||
purchase_orders.origin += ',' + productions.name
|
||||
if item['is_reprogramming'] is False:
|
||||
productions._create_workorder(item)
|
||||
productions.programming_state = '已编程'
|
||||
else:
|
||||
productions.programming_state = '编程中'
|
||||
return productions
|
||||
|
||||
# 在之前的销售单上重新生成制造订单
|
||||
def create_production1_values(self, production, sale_order):
|
||||
def create_production1_values(self, production):
|
||||
production_values_str = {'origin': production.origin,
|
||||
'product_id': production.product_id.id,
|
||||
'programming_state': '已编程',
|
||||
'product_description_variants': production.product_description_variants,
|
||||
'product_qty': production.product_qty,
|
||||
'product_uom_id': production.product_uom_id.id,
|
||||
@@ -1032,7 +1136,8 @@ class MrpProduction(models.Model):
|
||||
'date_deadline': production.date_deadline,
|
||||
'date_planned_start': production.date_planned_start,
|
||||
'date_planned_finished': production.date_planned_finished,
|
||||
'procurement_group_id': sale_order.id,
|
||||
# 'procurement_group_id': self.env["procurement.group"].create(
|
||||
# {'name': production.name}).id,
|
||||
'propagate_cancel': production.propagate_cancel,
|
||||
'orderpoint_id': production.orderpoint_id.id,
|
||||
'picking_type_id': production.picking_type_id.id,
|
||||
|
||||
@@ -21,7 +21,7 @@ class ResMrpRoutingWorkcenter(models.Model):
|
||||
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
|
||||
bom_id = fields.Many2one('mrp.bom', required=False)
|
||||
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
|
||||
|
||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||
def get_no(self):
|
||||
international_standards = self.search(
|
||||
[('code', '!=', ''), ('active', 'in', [True, False])],
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import datetime
|
||||
from datetime import timedelta, time
|
||||
from collections import defaultdict
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
from odoo.addons.resource.models.resource import Intervals
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
import math
|
||||
|
||||
|
||||
class ResWorkcenter(models.Model):
|
||||
@@ -41,13 +44,16 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
oee_target = fields.Float(
|
||||
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
|
||||
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month',
|
||||
store=True)
|
||||
|
||||
time_start = fields.Float('Setup Time', tracking=True)
|
||||
time_stop = fields.Float('Cleanup Time', tracking=True)
|
||||
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
|
||||
|
||||
equipment_status = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
string="设备状态", related='equipment_id.state')
|
||||
|
||||
# @api.depends('equipment_id')
|
||||
@@ -124,6 +130,108 @@ class ResWorkcenter(models.Model):
|
||||
res[wc_id] = [(datetime.fromtimestamp(s), datetime.fromtimestamp(e)) for s, e, _ in final_intervals_wc]
|
||||
return res
|
||||
|
||||
# AGV是否可配送
|
||||
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
|
||||
# 生产线优化
|
||||
available_machine_number = fields.Integer(string="可用机台数量")
|
||||
single_machine_capacity = fields.Float(string="单台小时产能")
|
||||
production_line_hour_capacity = fields.Float(string="生产线小时产能", readonly=True,
|
||||
compute='_compute_production_line_hour_capacity')
|
||||
effective_working_hours_day = fields.Float(string="日有效工作时长", default=0, readonly=True,
|
||||
compute='_compute_effective_working_hours_day')
|
||||
default_capacity = fields.Float(
|
||||
string='生产线日产能', compute='_compute_production_line_day_capacity', readonly=True)
|
||||
|
||||
# 计算生产线日产能
|
||||
@api.depends('production_line_hour_capacity', 'effective_working_hours_day')
|
||||
def _compute_production_line_day_capacity(self):
|
||||
for record in self:
|
||||
record.default_capacity = round(
|
||||
record.production_line_hour_capacity * record.effective_working_hours_day, 2)
|
||||
|
||||
# 计算日有效工作时长
|
||||
@api.depends('resource_calendar_id', 'resource_calendar_id.attendance_ids',
|
||||
'resource_calendar_id.attendance_ids.hour_to', 'resource_calendar_id.attendance_ids.hour_from')
|
||||
def _compute_effective_working_hours_day(self):
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == self.get_current_day_of_week(datetime.datetime.now())]
|
||||
if attendance_ids:
|
||||
for attendance_id in attendance_ids:
|
||||
if attendance_id.hour_from and attendance_id.hour_to:
|
||||
record.effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
|
||||
else:
|
||||
record.effective_working_hours_day = 0
|
||||
|
||||
# 获取传入时间是星期几
|
||||
def get_current_day_of_week(self, datetime):
|
||||
day_num = datetime.weekday()
|
||||
return str(day_num)
|
||||
|
||||
# 计算生产线小时产能
|
||||
@api.depends('single_machine_capacity', 'available_machine_number')
|
||||
def _compute_production_line_hour_capacity(self):
|
||||
for record in self:
|
||||
record.production_line_hour_capacity = round(
|
||||
record.single_machine_capacity * record.available_machine_number, 2)
|
||||
|
||||
# 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
def deal_with_workcenter_calendar(self, start_date):
|
||||
start_date = start_date + timedelta(hours=8) # 转换为北京时间
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == record.get_current_day_of_week(start_date) and self.is_between_times(
|
||||
p.hour_from, p.hour_to, start_date)]
|
||||
return False if not attendance_ids else True
|
||||
|
||||
# 判断传入时间是否在配置的工作中心的工作日历内
|
||||
def is_between_times(self, hour_from, hour_to, start_date):
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_from)
|
||||
start_time = time(integer_part, decimal_part)
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_to)
|
||||
end_time = time(integer_part, decimal_part)
|
||||
return start_time <= start_date.time() <= end_time
|
||||
|
||||
# 获取整数部分和小数部分
|
||||
def get_integer_and_decimal_parts(self, value):
|
||||
integer_part = int(value)
|
||||
decimal_part = value - integer_part
|
||||
if decimal_part > 0:
|
||||
decimal_part = round(decimal_part, 2) * 60
|
||||
return int(integer_part), math.ceil(decimal_part)
|
||||
|
||||
# 处理排程是否超过日产能
|
||||
def deal_available_default_capacity(self, date_planned):
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d')
|
||||
date_planned_end = date_planned + timedelta(days=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end),
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.default_capacity:
|
||||
return False
|
||||
return True
|
||||
|
||||
# 处理排程是否超过小时产能
|
||||
def deal_available_single_machine_capacity(self, date_planned):
|
||||
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
|
||||
date_planned_end = date_planned + timedelta(hours=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d %H:00:00')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end),
|
||||
('state', 'not in', ['draft', 'cancel'])])
|
||||
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.production_line_hour_capacity:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ResWorkcenterProductivity(models.Model):
|
||||
_inherit = 'mrp.workcenter.productivity'
|
||||
|
||||
@@ -58,9 +58,15 @@ class ResMrpWorkOrder(models.Model):
|
||||
('cancel', '取消')], string='Status',
|
||||
compute='_compute_state', store=True,
|
||||
default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True)
|
||||
|
||||
# state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True)
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
|
||||
@api.depends('production_id.manual_quotation')
|
||||
def _compute_manual_quotation(self):
|
||||
for item in self:
|
||||
item.manual_quotation = item.production_id.manual_quotation
|
||||
|
||||
manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True)
|
||||
|
||||
def _compute_working_users(self):
|
||||
super()._compute_working_users()
|
||||
@@ -131,13 +137,34 @@ class ResMrpWorkOrder(models.Model):
|
||||
is_subcontract = fields.Boolean(string='是否外协')
|
||||
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数")
|
||||
picking_ids = fields.Many2many('stock.picking', string='外协出入库单')
|
||||
# purchase_id = fields.Many2one('purchase.order', string='外协采购单')
|
||||
surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids')
|
||||
surface_technics_purchase_count = fields.Integer("外协采购", compute='_compute_surface_technics_purchase_ids')
|
||||
|
||||
# 是否绑定托盘
|
||||
is_trayed = fields.Boolean(string='是否绑定托盘', default=False)
|
||||
|
||||
tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True)
|
||||
|
||||
@api.depends('name', 'production_id.name')
|
||||
def _compute_surface_technics_picking_ids(self):
|
||||
for order in self:
|
||||
picking_ids = self.env['stock.picking'].search([('id', 'in', order.picking_ids.ids)])
|
||||
order.surface_technics_picking_count = len(picking_ids)
|
||||
for workorder in self:
|
||||
if workorder.routing_type == '表面工艺':
|
||||
domain = [('origin', '=', workorder.production_id.name)]
|
||||
previous_workorder = self.env['mrp.workorder'].search(
|
||||
[('sequence', '=', workorder.sequence - 1), ('routing_type', '=', '表面工艺'),
|
||||
('production_id', '=', workorder.production_id.id)])
|
||||
if previous_workorder:
|
||||
process_product = self.env['product.template']._get_process_parameters_product(
|
||||
previous_workorder.surface_technics_parameters_id)
|
||||
domain += [('partner_id', '=', process_product.partner_id.id)]
|
||||
else:
|
||||
domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)]
|
||||
picking_ids = self.env['stock.picking'].search(domain, order='id asc')
|
||||
workorder.surface_technics_picking_count = len(picking_ids)
|
||||
workorder.picking_ids = picking_ids.ids
|
||||
else:
|
||||
workorder.surface_technics_picking_count = 0
|
||||
|
||||
def action_view_surface_technics_picking(self):
|
||||
self.ensure_one()
|
||||
@@ -153,6 +180,38 @@ class ResMrpWorkOrder(models.Model):
|
||||
action['context'] = dict(self._context, default_origin=self.name)
|
||||
return action
|
||||
|
||||
@api.depends('state', 'production_id.name')
|
||||
def _compute_surface_technics_purchase_ids(self):
|
||||
for order in self:
|
||||
if order.routing_type == '表面工艺':
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', order.production_id.programming_no)], order='name asc')
|
||||
production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False)
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
for line in purchase.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id and line.product_qty == len(
|
||||
production_no_remanufacture):
|
||||
order.surface_technics_purchase_count = len(purchase)
|
||||
else:
|
||||
order.surface_technics_purchase_count = 0
|
||||
|
||||
def action_view_surface_technics_purchase(self):
|
||||
self.ensure_one()
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
|
||||
result = {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "purchase.order",
|
||||
"res_id": purchase_orders.id,
|
||||
# "domain": [['id', 'in', self.purchase_id]],
|
||||
"name": _("Purchase Orders"),
|
||||
'view_mode': 'form',
|
||||
}
|
||||
return result
|
||||
|
||||
supplier_id = fields.Many2one('res.partner', string='外协供应商')
|
||||
equipment_id = fields.Many2one('maintenance.equipment', string='加工设备', tracking=True)
|
||||
# 保存名称
|
||||
@@ -165,7 +224,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
material_width = fields.Float(string='宽')
|
||||
material_height = fields.Float(string='高')
|
||||
# 零件图号
|
||||
part_number = fields.Char(string='零件图号')
|
||||
part_number = fields.Char(related='production_id.part_number', string='零件图号')
|
||||
# 工序状态
|
||||
process_state = fields.Selection([
|
||||
('待装夹', '待装夹'),
|
||||
@@ -181,6 +240,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0',
|
||||
store=True, compute='_compute_tool_state')
|
||||
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
|
||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||
|
||||
@api.depends('cnc_ids.tool_state')
|
||||
def _compute_tool_state_remark(self):
|
||||
@@ -318,10 +378,10 @@ class ResMrpWorkOrder(models.Model):
|
||||
vals['leave_id'] = leave.id
|
||||
self.write(vals)
|
||||
|
||||
@api.onchange('rfid_code')
|
||||
def _onchange(self):
|
||||
if self.rfid_code and self.state == 'progress':
|
||||
self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
|
||||
# @api.onchange('rfid_code')
|
||||
# def _onchange(self):
|
||||
# if self.rfid_code and self.state == 'progress':
|
||||
# self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
|
||||
|
||||
def get_plan_workorder(self, production_line):
|
||||
tomorrow = (date.today() + timedelta(days=+1)).strftime("%Y-%m-%d")
|
||||
@@ -356,12 +416,10 @@ class ResMrpWorkOrder(models.Model):
|
||||
|
||||
# 获取三次元检测点数据
|
||||
def get_three_check_datas(self):
|
||||
factory_nick_name = 'XT'
|
||||
ftp_resconfig = self.env['res.config.settings'].get_values()
|
||||
ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']),
|
||||
ftp_resconfig['ftp_user'],
|
||||
ftp_resconfig['ftp_password'])
|
||||
# ftp.connect()
|
||||
|
||||
local_dir_path = '/ftp/before'
|
||||
os.makedirs(local_dir_path, exist_ok=True)
|
||||
@@ -370,6 +428,18 @@ class ResMrpWorkOrder(models.Model):
|
||||
logging.info('local_file_path:%s' % local_file_path)
|
||||
remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename
|
||||
logging.info('remote_path:%s' % remote_path)
|
||||
is_get_detection_file = self.env['ir.config_parameter'].sudo().get_param('is_get_detection_file')
|
||||
if not is_get_detection_file:
|
||||
paload_data = {
|
||||
"filename": local_filename
|
||||
}
|
||||
if not ftp_resconfig['get_check_file_path']:
|
||||
raise UserError('请先配置获取检测报告地址')
|
||||
url = ftp_resconfig['get_check_file_path'] + '/get/check/report'
|
||||
response = requests.post(url, json=paload_data)
|
||||
logging.info('response:%s' % response.json())
|
||||
if response.json().get('detail'):
|
||||
raise UserError(response.json().get('detail'))
|
||||
|
||||
if not ftp.file_exists(remote_path):
|
||||
raise UserError(f"文件不存在: {remote_path}")
|
||||
@@ -482,6 +552,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
raise UserError('PT10点未测或数据错误')
|
||||
|
||||
self.data_state = True
|
||||
self.getcenter()
|
||||
|
||||
return True
|
||||
|
||||
@@ -535,6 +606,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
print("(%.2f,%.2f)" % (x, y))
|
||||
self.material_center_point = ("(%.2f,%.2f,%.2f)" % (x, y, z))
|
||||
self.X_deviation_angle = jdz
|
||||
logging.info("坯料中心点坐标:(%.2f,%.2f)" % (x, y))
|
||||
logging.info("X轴偏差度数:%.2f" % jdz)
|
||||
# 将补偿值写入CNC加工工单
|
||||
workorder = self.env['mrp.workorder'].browse(self.ids)
|
||||
work = workorder.production_id.workorder_ids
|
||||
@@ -590,29 +663,36 @@ class ResMrpWorkOrder(models.Model):
|
||||
# 拼接工单对象属性值
|
||||
def json_workorder_str(self, k, production, route, item):
|
||||
# 计算预计时长duration_expected
|
||||
if route.routing_type == '切割':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', '切割')]).time_cycle
|
||||
# elif route.routing_type == '获取CNC加工程序':
|
||||
routing_types = ['切割', '装夹预调', 'CNC加工', '解除装夹']
|
||||
if route.routing_type in routing_types:
|
||||
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', route.routing_type)])
|
||||
duration_expected = routing_workcenter.time_cycle
|
||||
reserved_duration = routing_workcenter.reserved_duration
|
||||
# if route.routing_type == '切割':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '获取CNC加工程序')]).time_cycle
|
||||
elif route.routing_type == '装夹预调':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', '装夹预调')]).time_cycle
|
||||
# elif route.routing_type == '前置三元定位检测':
|
||||
# [('name', '=', '切割')]).time_cycle
|
||||
# # elif route.routing_type == '获取CNC加工程序':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '获取CNC加工程序')]).time_cycle
|
||||
# elif route.routing_type == '装夹预调':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '前置三元定位检测')]).time_cycle
|
||||
elif route.routing_type == 'CNC加工':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', 'CNC加工')]).time_cycle
|
||||
# elif route.routing_type == '后置三元质量检测':
|
||||
# [('name', '=', '装夹预调')]).time_cycle
|
||||
# # elif route.routing_type == '前置三元定位检测':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '前置三元定位检测')]).time_cycle
|
||||
# elif route.routing_type == 'CNC加工':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '后置三元质量检测')]).time_cycle
|
||||
elif route.routing_type == '解除装夹':
|
||||
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
[('name', '=', '解除装夹')]).time_cycle
|
||||
# [('name', '=', 'CNC加工')]).time_cycle
|
||||
# # elif route.routing_type == '后置三元质量检测':
|
||||
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# # [('name', '=', '后置三元质量检测')]).time_cycle
|
||||
# elif route.routing_type == '解除装夹':
|
||||
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
|
||||
# [('name', '=', '解除装夹')]).time_cycle
|
||||
else:
|
||||
duration_expected = 60
|
||||
reserved_duration = 30
|
||||
workorders_values_str = [0, '', {
|
||||
'product_uom_id': production.product_uom_id.id,
|
||||
'qty_producing': 0,
|
||||
@@ -630,29 +710,41 @@ class ResMrpWorkOrder(models.Model):
|
||||
'date_planned_finished': datetime.now() + timedelta(days=1),
|
||||
'duration_expected': duration_expected,
|
||||
'duration': 0,
|
||||
'tag_type': '重新加工' if item is False else False,
|
||||
'cnc_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cnc.processing']._json_cnc_processing(
|
||||
k, item),
|
||||
'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k,
|
||||
item),
|
||||
'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
|
||||
production)
|
||||
# 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
|
||||
# production)
|
||||
'reserved_duration': reserved_duration,
|
||||
}]
|
||||
return workorders_values_str
|
||||
|
||||
def _json_workpiece_delivery_list(self, production):
|
||||
up_route = self.env['sf.agv.task.route'].search([('route_type', '=', '上产线')], limit=1, order='id asc')
|
||||
down_route = self.env['sf.agv.task.route'].search([('route_type', '=', '下产线')], limit=1, order='id asc')
|
||||
def _json_workpiece_delivery_list(self):
|
||||
# 修改在装夹工单完成后,生成上产线的工件配送单
|
||||
|
||||
# up_route = self.env['sf.agv.task.route'].search([('route_type', '=', '上产线')], limit=1, order='id asc')
|
||||
# down_route = self.env['sf.agv.task.route'].search([('route_type', '=', '下产线')], limit=1, order='id asc')
|
||||
return [
|
||||
[0, '',
|
||||
{'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '上产线',
|
||||
'route_id': up_route.id,
|
||||
'feeder_station_start_id': up_route.start_site_id.id,
|
||||
'feeder_station_destination_id': up_route.end_site_id.id}],
|
||||
[0, '',
|
||||
{'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '下产线',
|
||||
'route_id': down_route.id,
|
||||
'feeder_station_start_id': down_route.start_site_id.id,
|
||||
'feeder_station_destination_id': down_route.end_site_id.id}]]
|
||||
{
|
||||
'production_id': self.production_id.id,
|
||||
'production_line_id': self.production_id.production_line_id.id,
|
||||
'type': '上产线',
|
||||
'is_cnc_program_down': True,
|
||||
'rfid_code': self.rfid_code
|
||||
# 'route_id': up_route.id,
|
||||
# 'feeder_station_start_id': agv_start_site_id,
|
||||
# 'feeder_station_destination_id': up_route.end_site_id.id
|
||||
}
|
||||
],
|
||||
# [0, '',
|
||||
# {'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '下产线',
|
||||
# 'route_id': down_route.id,
|
||||
# 'feeder_station_start_id': down_route.start_site_id.id,
|
||||
# 'feeder_station_destination_id': down_route.end_site_id.id}]
|
||||
]
|
||||
|
||||
# 拼接工单对象属性值(表面工艺)
|
||||
def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id):
|
||||
@@ -868,7 +960,7 @@ class ResMrpWorkOrder(models.Model):
|
||||
workorder.state = 'waiting'
|
||||
elif workorder.routing_type == '解除装夹' and workorder.state not in ['done', 'rework', 'cancel']:
|
||||
if cnc_workorder:
|
||||
if not cnc_workorder_pending:
|
||||
if not cnc_workorder_pending or unclamp_workorder.test_results == '报废':
|
||||
workorder.state = 'waiting'
|
||||
# else:
|
||||
# if workorder.production_id.is_rework is True:
|
||||
@@ -885,10 +977,26 @@ class ResMrpWorkOrder(models.Model):
|
||||
# workorder.state = 'ready'
|
||||
if workorder.routing_type == '表面工艺' and workorder.state not in ['done', 'progress']:
|
||||
if unclamp_workorder:
|
||||
workorder.state = 'ready'
|
||||
# else:
|
||||
# if workorder.state not in ['cancel', 'rework']:
|
||||
# workorder.state = 'rework'
|
||||
if workorder.is_subcontract is False:
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
|
||||
production_no_remanufacture = production_programming.filtered(
|
||||
lambda a: a.is_remanufacture is False)
|
||||
production_list = [production.name for production in production_programming]
|
||||
purchase_orders = self.env['purchase.order'].search(
|
||||
[('origin', 'ilike', ','.join(production_list))])
|
||||
for line in purchase_orders.order_line:
|
||||
if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id and line.product_qty == len(
|
||||
production_no_remanufacture):
|
||||
if purchase_orders.state == 'purchase':
|
||||
workorder.state = 'ready'
|
||||
else:
|
||||
workorder.state = 'waiting'
|
||||
elif workorder.production_id.state == 'scrap':
|
||||
if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废':
|
||||
workorder.state = 'waiting'
|
||||
if workorder.routing_type == '装夹预调' and workorder.state in ['waiting', 'ready', 'pending']:
|
||||
workorder_ids = workorder.production_id.workorder_ids
|
||||
work_bo = True
|
||||
@@ -923,16 +1031,20 @@ class ResMrpWorkOrder(models.Model):
|
||||
# 查询工序最小的非完工、非返工的装夹预调工单
|
||||
work_id = self.search(
|
||||
[('production_id', '=', workorder.production_id.id),
|
||||
('routing_type', '=', '装夹预调'),
|
||||
('state', 'not in', ['rework', 'done', 'cancel'])],
|
||||
limit=1,
|
||||
order="sequence")
|
||||
if workorder == work_id:
|
||||
if workorder.production_id.reservation_state == 'assigned':
|
||||
workorder.state = 'ready'
|
||||
elif workorder.production_id.reservation_state != 'assigned':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
if work_id.routing_type == '装夹预调':
|
||||
if workorder == work_id:
|
||||
if workorder.production_id.reservation_state == 'assigned':
|
||||
workorder.state = 'ready'
|
||||
elif workorder.production_id.reservation_state != 'assigned':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
elif (workorder.name == '装夹预调' and
|
||||
workorder.state not in ['rework', 'done', 'cancel']):
|
||||
if workorder.state != 'pending':
|
||||
workorder.state = 'pending'
|
||||
if workorder.production_id.tool_state in ['1', '2'] and workorder.state == 'ready':
|
||||
workorder.state = 'waiting'
|
||||
continue
|
||||
@@ -1007,8 +1119,8 @@ class ResMrpWorkOrder(models.Model):
|
||||
('location_dest_id', '=', self.env['stock.location'].search(
|
||||
[('barcode', 'ilike', 'VL-SPOC')]).id),
|
||||
('origin', '=', self.production_id.name)])
|
||||
if move_out:
|
||||
move_out.write({'state': 'assigned'})
|
||||
if move_out.state != 'done':
|
||||
move_out.write({'state': 'assigned', 'production_id': False})
|
||||
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
|
||||
|
||||
# move_out._action_assign()
|
||||
@@ -1074,23 +1186,33 @@ class ResMrpWorkOrder(models.Model):
|
||||
def button_finish(self):
|
||||
for record in self:
|
||||
if record.routing_type == '装夹预调':
|
||||
if not record.material_center_point and record.X_deviation_angle > 0:
|
||||
raise UserError("请对前置三元检测定位参数进行计算定位")
|
||||
if not record.rfid_code and record.is_rework is False:
|
||||
raise UserError("请扫RFID码进行绑定")
|
||||
if record.is_rework is False:
|
||||
if not record.material_center_point:
|
||||
raise UserError("坯料中心点为空,请检查")
|
||||
if record.X_deviation_angle <= 0:
|
||||
raise UserError("X偏差角度小于等于0,请检查!本次计算的X偏差角度为:%s" % record.X_deviation_angle)
|
||||
record.process_state = '待加工'
|
||||
# record.write({'process_state': '待加工'})
|
||||
record.production_id.process_state = '待加工'
|
||||
# 生成工件配送单
|
||||
record.workpiece_delivery_ids = record._json_workpiece_delivery_list()
|
||||
if record.routing_type == 'CNC加工':
|
||||
record.process_state = '待解除装夹'
|
||||
# record.write({'process_state': '待加工'})
|
||||
record.production_id.process_state = '待解除装夹'
|
||||
self.env['sf.production.plan'].sudo().search([('name', '=', record.production_id.name)]).write({
|
||||
'state': 'finished',
|
||||
'actual_end_time': datetime.now()
|
||||
})
|
||||
record.production_id.write({'detection_result_ids': [(0, 0, {
|
||||
'rework_reason': record.reason,
|
||||
'detailed_reason': record.detailed_reason,
|
||||
'processing_panel': record.processing_panel,
|
||||
'routing_type': record.routing_type,
|
||||
'handle_result': '待处理' if record.test_results == '返工' or record.is_rework is True else '',
|
||||
'handle_result': '待处理' if record.test_results in ['返工',
|
||||
'报废'] or record.is_rework is True else '',
|
||||
'test_results': record.test_results,
|
||||
'test_report': record.detection_report})],
|
||||
'is_scrap': True if record.test_results == '报废' else False})
|
||||
@@ -1110,28 +1232,13 @@ class ResMrpWorkOrder(models.Model):
|
||||
picking_out = record.env['stock.move.line'].search(
|
||||
[('picking_id', '=', record.picking_ids[0].id)])
|
||||
logging.info('picking_out:%s' % picking_out.picking_id.name)
|
||||
if picking_out:
|
||||
order_line_ids = []
|
||||
logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name)
|
||||
server_product = self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', record.surface_technics_parameters_id.id),
|
||||
('detailed_type', '=', 'service')])
|
||||
logging.info('server_product:%s' % server_product.name)
|
||||
if server_product:
|
||||
order_line_ids.append((0, 0, {
|
||||
'product_id': server_product.product_variant_id.id,
|
||||
'product_qty': 1,
|
||||
'product_uom': server_product.uom_id.id
|
||||
}))
|
||||
self.env['purchase.order'].sudo().create({
|
||||
'partner_id': server_product.seller_ids.partner_id.id,
|
||||
'origin': record.production_id.name,
|
||||
'state': 'draft',
|
||||
'order_line': order_line_ids,
|
||||
})
|
||||
else:
|
||||
raise UserError(
|
||||
'请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name)
|
||||
# if picking_out:
|
||||
# order_line_ids = []
|
||||
# logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name)
|
||||
#
|
||||
# else:
|
||||
# raise UserError(
|
||||
# '请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name)
|
||||
tem_date_planned_finished = record.date_planned_finished
|
||||
tem_date_finished = record.date_finished
|
||||
logging.info('routing_type:%s' % record.routing_type)
|
||||
@@ -1183,6 +1290,17 @@ class ResMrpWorkOrder(models.Model):
|
||||
record.production_id.button_mark_done1()
|
||||
# record.production_id.state = 'done'
|
||||
|
||||
# 解绑托盘
|
||||
def unbind_tray(self):
|
||||
self.production_id.workorder_ids.write({
|
||||
'rfid_code': False,
|
||||
'tray_serial_number': False,
|
||||
'tray_product_id': False,
|
||||
'tray_brand_id': False,
|
||||
'tray_type_id': False,
|
||||
'tray_model_id': False,
|
||||
'is_trayed': False})
|
||||
|
||||
# 将FTP的检测报告文件下载到临时目录
|
||||
def download_reportfile_tmp(self, workorder, reportpath):
|
||||
logging.info('reportpath/ftp地址:%s' % reportpath)
|
||||
@@ -1222,6 +1340,73 @@ class ResMrpWorkOrder(models.Model):
|
||||
else:
|
||||
raise UserError("无关联制造订单或关联序列号,无法打印。请检查!")
|
||||
|
||||
@api.model
|
||||
def get_views(self, views, options=None):
|
||||
res = super().get_views(views, options)
|
||||
if res['views'].get('list', {}) and self.env.context.get('search_default_workcenter_id'):
|
||||
workcenter = self.env['mrp.workcenter'].browse(self.env.context.get('search_default_workcenter_id'))
|
||||
tree_view = res['views']['list']
|
||||
if workcenter.name == '工件拆卸中心':
|
||||
arch = etree.fromstring(tree_view['arch'])
|
||||
# 查找 tree 标签
|
||||
tree_element = arch.xpath("//tree")[0]
|
||||
tree_element.set('js_class', 'remove_focus_list_view')
|
||||
|
||||
# 查找或创建 header 标签
|
||||
header_element = tree_element.find('header')
|
||||
if header_element is None:
|
||||
header_element = etree.Element('header')
|
||||
tree_element.insert(0, header_element)
|
||||
|
||||
# 创建并添加按钮元素
|
||||
button_element = etree.Element('button', {
|
||||
'name': 'button_delivery',
|
||||
'type': 'object',
|
||||
'string': '解除装夹',
|
||||
'class': 'btn-primary jikimo_button_confirm',
|
||||
# 'className': 'btn-primary',
|
||||
'modifiers': '{"force_show": 1}'
|
||||
})
|
||||
header_element.append(button_element)
|
||||
|
||||
# 更新 tree_view 的 arch
|
||||
tree_view['arch'] = etree.tostring(arch, encoding='unicode')
|
||||
return res
|
||||
|
||||
def button_delivery(self):
|
||||
# production_ids = []
|
||||
# workorder_ids = []
|
||||
delivery_type = '运送空料架'
|
||||
# max_num = 4 # 最大配送数量
|
||||
# feeder_station_start_id = False
|
||||
# if len(self) > max_num:
|
||||
# raise UserError('仅限于拆卸1-4个制造订单,请重新选择')
|
||||
# for item in self:
|
||||
# if item.state != 'ready':
|
||||
# raise UserError('请选择状态为【就绪】的工单进行解除装夹')
|
||||
#
|
||||
# production_ids.append(item.production_id.id)
|
||||
# workorder_ids.append(item.id)
|
||||
# if not feeder_station_start_id:
|
||||
# down_product_agv_scheduling = item.get_down_product_agv_scheduling()
|
||||
# if down_product_agv_scheduling:
|
||||
# feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
|
||||
return {
|
||||
'name': _('确认'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.workpiece.delivery.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
# 'default_delivery_ids': [(6, 0, delivery_ids)],
|
||||
# 'default_production_ids': [(6, 0, production_ids)],
|
||||
'default_delivery_type': delivery_type,
|
||||
# 'default_workorder_ids': [(6, 0, workorder_ids)],
|
||||
'default_workcenter_id': self.env.context.get('default_workcenter_id'),
|
||||
'default_confirm_button': '确认解除',
|
||||
# 'default_feeder_station_start_id': feeder_station_start_id,
|
||||
}}
|
||||
|
||||
|
||||
class CNCprocessing(models.Model):
|
||||
_name = 'sf.cnc.processing'
|
||||
@@ -1388,6 +1573,8 @@ class SfWorkOrderBarcodes(models.Model):
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
logging.info('Rfid:%s' % barcode)
|
||||
if 'O-CMD' in barcode:
|
||||
return None
|
||||
workorder = self.env['mrp.workorder'].browse(self.ids)
|
||||
# workorder_preset = self.env['mrp.workorder'].search(
|
||||
# [('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)])
|
||||
@@ -1430,6 +1617,7 @@ class SfWorkOrderBarcodes(models.Model):
|
||||
raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name))
|
||||
self.process_state = '待检测'
|
||||
self.date_start = datetime.now()
|
||||
self.is_trayed = True
|
||||
else:
|
||||
raise UserError('没有找到Rfid为【%s】的托盘信息!!!' % barcode)
|
||||
# stock_move_line = self.env['stock.move.line'].search([('lot_name', '=', barcode)])
|
||||
@@ -1501,20 +1689,24 @@ class WorkPieceDelivery(models.Model):
|
||||
feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
|
||||
task_delivery_time = fields.Datetime('任务下发时间')
|
||||
task_completion_time = fields.Datetime('任务完成时间')
|
||||
type = fields.Selection(
|
||||
[('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
|
||||
|
||||
def _get_agv_route_type_selection(self):
|
||||
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
|
||||
|
||||
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
|
||||
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
|
||||
status = fields.Selection(
|
||||
[('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
|
||||
default='待下发',
|
||||
tracking=True)
|
||||
[('待下发', '待下发'), ('已下发', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
|
||||
default='待下发', tracking=True)
|
||||
is_cnc_program_down = fields.Boolean('程序是否下发', default=False, tracking=True)
|
||||
is_manual_work = fields.Boolean('人工操作', default=False)
|
||||
active = fields.Boolean(string="有效", default=True)
|
||||
|
||||
agv_scheduling_id = fields.Many2one('sf.agv.scheduling', 'AGV任务调度')
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals['route_id'] and vals.get('type') is None:
|
||||
if vals.get('route_id') and vals.get('type') is None:
|
||||
vals['type'] = '运送空料架'
|
||||
else:
|
||||
if vals.get('name', '/') == '/' or vals.get('name', '/') is False:
|
||||
@@ -1526,14 +1718,14 @@ class WorkPieceDelivery(models.Model):
|
||||
obj.feeder_station_start_id.name, obj.feeder_station_destination_id.name)
|
||||
return obj
|
||||
|
||||
@api.constrains('route_id')
|
||||
def _check_route_id(self):
|
||||
if self.type == '运送空料架':
|
||||
if self.route_id and self.name is False:
|
||||
route = self.sudo().search(
|
||||
[('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')])
|
||||
if route:
|
||||
raise UserError("该任务路线已存在,请重新选择")
|
||||
# @api.constrains('route_id')
|
||||
# def _check_route_id(self):
|
||||
# if self.type == '运送空料架':
|
||||
# if self.route_id and self.name is False:
|
||||
# route = self.sudo().search(
|
||||
# [('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')])
|
||||
# if route:
|
||||
# raise UserError("该任务路线已存在,请重新选择")
|
||||
|
||||
# @api.constrains('name')
|
||||
# def _check_name(self):
|
||||
@@ -1562,84 +1754,44 @@ class WorkPieceDelivery(models.Model):
|
||||
def button_delivery(self):
|
||||
delivery_ids = []
|
||||
production_ids = []
|
||||
workorder_ids = []
|
||||
is_cnc_down = 0
|
||||
is_not_production_line = 0
|
||||
is_not_route = 0
|
||||
same_production_line_id = None
|
||||
same_route_id = None
|
||||
down_status = '待下发'
|
||||
production_type = None
|
||||
num = 0
|
||||
delivery_type = '上产线'
|
||||
max_num = 4 # 最大配送数量
|
||||
if len(self) > max_num:
|
||||
raise UserError('仅限于配送1-4个制造订单,请重新选择')
|
||||
for item in self:
|
||||
num += 1
|
||||
if production_type is None:
|
||||
production_type = item.type
|
||||
if item.type == "运送空料架":
|
||||
if num >= 2:
|
||||
raise UserError('仅选择一条路线进行配送,请重新选择')
|
||||
else:
|
||||
delivery_ids.append(item.id)
|
||||
else:
|
||||
if num > 4:
|
||||
raise UserError('仅限于配送1-4个制造订单,请重新选择')
|
||||
if item.status in ['待配送', '已配送']:
|
||||
raise UserError('请选择状态为【待下发】的制造订单进行配送')
|
||||
if item.route_id:
|
||||
if same_route_id is None:
|
||||
same_route_id = item.route_id.id
|
||||
if item.route_id.id != same_route_id:
|
||||
is_not_route += 1
|
||||
# else:
|
||||
# raise UserError('请选择【任务路线】再进行配送')
|
||||
# if item.production_id.production_line_state == '已下产线' and item.state == '待下发' and item.type == '下产线':
|
||||
# raise UserError('该制造订单已下产线,无需配送')
|
||||
if production_type != item.type:
|
||||
raise UserError('请选择类型为%s的制造订单进行配送' % production_type)
|
||||
if down_status != item.status:
|
||||
up_workpiece = self.search([('type', '=', '上产线'), ('production_id', '=', item.production_id),
|
||||
('status', '=', '待下发')])
|
||||
if up_workpiece:
|
||||
raise UserError('您所选择的制造订单暂未上产线,请在上产线后再进行配送')
|
||||
else:
|
||||
raise UserError('请选择状态为【待下发】的制造订单进行配送')
|
||||
|
||||
if same_production_line_id is None:
|
||||
same_production_line_id = item.production_line_id.id
|
||||
if item.production_line_id.id != same_production_line_id:
|
||||
is_not_production_line += 1
|
||||
if item.is_cnc_program_down is False:
|
||||
is_cnc_down += 1
|
||||
if is_cnc_down == 0 and is_not_production_line == 0 and is_not_route == 0:
|
||||
delivery_ids.append(item.id)
|
||||
production_ids.append(item.production_id.id)
|
||||
if item.status != '待下发':
|
||||
raise UserError('请选择状态为【待下发】的制造订单进行配送')
|
||||
if same_production_line_id is None:
|
||||
same_production_line_id = item.production_line_id.id
|
||||
if item.production_line_id.id != same_production_line_id:
|
||||
is_not_production_line += 1
|
||||
if item.is_cnc_program_down is False:
|
||||
is_cnc_down += 1
|
||||
if is_cnc_down == 0 and is_not_production_line == 0:
|
||||
delivery_ids.append(item.id)
|
||||
production_ids.append(item.production_id.id)
|
||||
workorder_ids.append(item.workorder_id.id)
|
||||
if is_cnc_down >= 1:
|
||||
raise UserError('您所选择制造订单的【CNC程序】暂未下发,请在程序下发后再进行配送')
|
||||
if is_not_production_line >= 1:
|
||||
raise UserError('您所选择制造订单的【目的生产线】不一致,请重新确认')
|
||||
if is_not_route >= 1:
|
||||
raise UserError('您所选择制造订单的【任务路线】不一致,请重新确认')
|
||||
is_free = self._check_avgsite_state()
|
||||
if is_free is True:
|
||||
if delivery_ids:
|
||||
return {
|
||||
'name': _('确认'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.workpiece.delivery.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_delivery_ids': [(6, 0, delivery_ids)],
|
||||
'default_production_ids': [(6, 0, production_ids)],
|
||||
'default_destination_production_line_id': same_production_line_id,
|
||||
'default_route_id': same_route_id,
|
||||
'default_type': production_type,
|
||||
}}
|
||||
else:
|
||||
if production_type == '运送空料架':
|
||||
raise UserError("您所选择的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时进行配送")
|
||||
else:
|
||||
raise UserError(
|
||||
"您所选择制造订单的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时或选择其他路线进行配送")
|
||||
return {
|
||||
'name': _('确认'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.workpiece.delivery.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_delivery_ids': [(6, 0, delivery_ids)],
|
||||
'default_production_ids': [(6, 0, production_ids)],
|
||||
'default_delivery_type': delivery_type,
|
||||
'default_workorder_ids': [(6, 0, workorder_ids)],
|
||||
'default_confirm_button': '确认配送'
|
||||
}}
|
||||
|
||||
# 验证agv站点是否可用
|
||||
def _check_avgsite_state(self):
|
||||
|
||||
@@ -5,8 +5,10 @@ import base64
|
||||
import hashlib
|
||||
import os
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
from odoo.modules import get_resource_path
|
||||
|
||||
|
||||
from OCC.Extend.DataExchange import read_step_file
|
||||
from OCC.Extend.DataExchange import write_stl_file
|
||||
|
||||
@@ -106,6 +108,15 @@ class ResProductMo(models.Model):
|
||||
|
||||
name = fields.Char('产品名称', compute='_compute_tool_name', store=True, required=False)
|
||||
|
||||
@api.constrains('seller_ids')
|
||||
def _check_seller_ids(self):
|
||||
if self.categ_type == '表面工艺':
|
||||
if self.seller_ids:
|
||||
if self.seller_ids[0].price == 0.0:
|
||||
raise UserError("请在该产品【采购】中的【价格】进行输入")
|
||||
else:
|
||||
raise UserError("请在【采购】中输入供应商信息")
|
||||
|
||||
@api.depends('cutting_tool_model_id', 'specification_id')
|
||||
def _compute_tool_name(self):
|
||||
for item in self:
|
||||
@@ -113,6 +124,10 @@ class ResProductMo(models.Model):
|
||||
name = '%s%s' % (item.cutting_tool_model_id.name, item.specification_id.name)
|
||||
item.name = name
|
||||
|
||||
def _get_process_parameters_product(self, production_process):
|
||||
return self.env['product.template'].search(
|
||||
[('server_product_process_parameters_id', '=', production_process.id)]).seller_ids[0]
|
||||
|
||||
@api.onchange('cutting_tool_model_id')
|
||||
def _onchange_cutting_tool_model_id(self):
|
||||
for item in self:
|
||||
@@ -640,6 +655,10 @@ class ResProductMo(models.Model):
|
||||
'part_number': item.get('part_number') or '',
|
||||
'active': True,
|
||||
}
|
||||
tax_id = self.env['account.tax'].sudo().search(
|
||||
[('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')])
|
||||
if tax_id:
|
||||
vals.update({'taxes_id': [(6, 0, [int(tax_id)])]})
|
||||
copy_product_id.sudo().write(vals)
|
||||
product_id.product_tmpl_id.active = False
|
||||
return copy_product_id
|
||||
@@ -736,7 +755,11 @@ class ResProductMo(models.Model):
|
||||
# 产品名称唯一性校验
|
||||
for item in templates:
|
||||
if len(self.search([('name', '=', item.name)])) > 1:
|
||||
raise ValidationError('产品名称【%s】已存在' % item.name)
|
||||
raise UserError('产品名称【%s】已存在' % item.name)
|
||||
if item.categ_type == '表面工艺':
|
||||
if len(self.search([('server_product_process_parameters_id', '=',
|
||||
item.server_product_process_parameters_id.id)])) > 1:
|
||||
raise UserError('表面工艺参数为【%s】的产品已存在' % item.server_product_process_parameters_id.name)
|
||||
if "create_product_product" not in self._context:
|
||||
templates._create_variant_ids()
|
||||
|
||||
@@ -800,7 +823,7 @@ class ResProductFixture(models.Model):
|
||||
diameter = fields.Float('直径(mm)', digits=(16, 2))
|
||||
|
||||
# '零点卡盘' 字段
|
||||
weight = fields.Float('重量(mm)', digits=(16, 2))
|
||||
weight = fields.Float('重量(kg)', digits=(16, 2))
|
||||
orientation_dish_diameter = fields.Float('定位盘直径(mm)', digits=(16, 2))
|
||||
clamping_diameter = fields.Float('装夹直径(mm)', digits=(16, 2))
|
||||
clamping_num = fields.Selection([('1', '1'), ('2', '2'), ('4', '4'), ('6', '6'), ('8', '8')], string='装夹单元数')
|
||||
@@ -947,6 +970,7 @@ class SfMaintenanceEquipmentAndProductTemplate(models.Model):
|
||||
raise ValidationError("机床基坐标获取失败")
|
||||
|
||||
|
||||
|
||||
class SfMaintenanceEquipmentTool(models.Model):
|
||||
_name = 'maintenance.equipment.tool'
|
||||
_description = '机床刀位'
|
||||
|
||||
42
sf_manufacturing/models/res_config_setting.py
Normal file
42
sf_manufacturing/models/res_config_setting.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
agv_rcs_url = fields.Char(string='avg_rcs访问地址',
|
||||
default='http://172.16.10.114:8182/rcms/services/rest/hikRpcService/genAgvSchedulingTask')
|
||||
wbcode = fields.Char('地码')
|
||||
agv_code = fields.Char(string='agv编号')
|
||||
task_type_no = fields.Char('任务单类型编号')
|
||||
|
||||
is_agv_task_dispatch = fields.Boolean('是否下发AGV任务', default=False)
|
||||
# 是否重新获取检测文件
|
||||
is_get_detection_file = fields.Boolean(string='重新获取检测文件', default=False)
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
values = super(ResConfigSettings, self).get_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
agv_rcs_url = config.get_param('agv_rcs_url', default='')
|
||||
wbcode = config.get_param('wbcode', default='')
|
||||
agv_code = config.get_param('agv_code', default='')
|
||||
is_agv_task_dispatch = config.get_param('is_agv_task_dispatch')
|
||||
is_get_detection_file = config.get_param('is_get_detection_file')
|
||||
values.update(
|
||||
agv_rcs_url=agv_rcs_url,
|
||||
wbcode=wbcode,
|
||||
agv_code=agv_code,
|
||||
is_agv_task_dispatch=is_agv_task_dispatch,
|
||||
is_get_detection_file=is_get_detection_file
|
||||
)
|
||||
return values
|
||||
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
config.set_param("agv_rcs_url", self.agv_rcs_url or "")
|
||||
config.set_param("wbcode", self.wbcode or "")
|
||||
config.set_param("agv_code", self.agv_code or "")
|
||||
config.set_param("is_agv_task_dispatch", self.is_agv_task_dispatch or False)
|
||||
config.set_param("is_get_detection_file", self.is_get_detection_file or False)
|
||||
@@ -68,6 +68,7 @@ class StockRule(models.Model):
|
||||
|
||||
@api.model
|
||||
def _run_pull(self, procurements):
|
||||
logging.info(procurements)
|
||||
moves_values_by_company = defaultdict(list)
|
||||
mtso_products_by_locations = defaultdict(list)
|
||||
|
||||
@@ -168,7 +169,6 @@ class StockRule(models.Model):
|
||||
else:
|
||||
forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
|
||||
procure_method = 'make_to_stock'
|
||||
|
||||
move_values = rule._get_stock_move_values(*procurement)
|
||||
move_values['procure_method'] = procure_method
|
||||
moves_values_by_company[procurement.company_id.id].append(move_values)
|
||||
@@ -176,10 +176,10 @@ class StockRule(models.Model):
|
||||
for company_id, moves_values in moves_values_by_company.items():
|
||||
# create the move as SUPERUSER because the current user may not have the rights to do it (mto product
|
||||
# launched by a sale for example)
|
||||
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(moves_values)
|
||||
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
|
||||
moves_values)
|
||||
# Since action_confirm launch following procurement_group we should activate it.
|
||||
moves._action_confirm()
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
@@ -217,6 +217,23 @@ class StockRule(models.Model):
|
||||
(
|
||||
p.move_dest_ids.procure_method != 'make_to_order' and not
|
||||
p.move_raw_ids and not p.workorder_ids)).action_confirm()
|
||||
# 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区
|
||||
for production in productions:
|
||||
if production.picking_ids:
|
||||
product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id
|
||||
if product_type_id.name == '坯料':
|
||||
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
|
||||
if not location_id:
|
||||
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
|
||||
break
|
||||
for picking_id in production.picking_ids:
|
||||
if picking_id.picking_type_id.name == '内部调拨':
|
||||
if picking_id.location_dest_id.product_type != product_type_id:
|
||||
picking_id.location_dest_id = location_id.id
|
||||
elif picking_id.picking_type_id.name == '生产发料':
|
||||
if picking_id.location_id.product_type != product_type_id:
|
||||
picking_id.location_id = location_id.id
|
||||
|
||||
for production in productions:
|
||||
'''
|
||||
创建制造订单时生成序列号
|
||||
@@ -250,6 +267,11 @@ class StockRule(models.Model):
|
||||
workorder_duration += workorder.duration_expected
|
||||
|
||||
sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
|
||||
# 根据销售订单号查询快速订单
|
||||
quick_easy_order = self.env['quick.easy.order'].sudo().search([('sale_order_id', '=', sale_order.id)])
|
||||
if quick_easy_order:
|
||||
production.write({'part_number': quick_easy_order.part_drawing_number,
|
||||
'part_drawing': quick_easy_order.machining_drawings})
|
||||
if sale_order:
|
||||
# sale_order.write({'schedule_status': 'to schedule'})
|
||||
self.env['sf.production.plan'].sudo().with_company(company_id).create({
|
||||
@@ -271,14 +293,71 @@ class StockRule(models.Model):
|
||||
# 为同一个product_id创建一个生产订单名称列表
|
||||
product_id_to_production_names[product_id] = [production.name for production in all_production]
|
||||
for production_item in productions:
|
||||
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('product_id.id', '=', production_item.product_id.id),
|
||||
('origin', '=', production_item.origin)],
|
||||
limit=1, order='id asc')
|
||||
if production_item.product_id.id in product_id_to_production_names:
|
||||
if not production_programming.programming_no:
|
||||
if production_item.product_id.model_process_parameters_ids:
|
||||
is_purchase = False
|
||||
sorted_process_parameters = sorted(production_item.product_id.model_process_parameters_ids,
|
||||
key=lambda w: w.id)
|
||||
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
for i in range(len(sorted_process_parameters) - 1):
|
||||
if m == 0:
|
||||
is_purchase = False
|
||||
if self.env['product.template']._get_process_parameters_product(
|
||||
sorted_process_parameters[i]).partner_id == self.env[
|
||||
'product.template']._get_process_parameters_product(sorted_process_parameters[
|
||||
i + 1]).partner_id and \
|
||||
sorted_process_parameters[i].gain_way == '外协':
|
||||
if sorted_process_parameters[i] not in consecutive_process_parameters:
|
||||
consecutive_process_parameters.append(sorted_process_parameters[i])
|
||||
consecutive_process_parameters.append(sorted_process_parameters[i + 1])
|
||||
m += 1
|
||||
continue
|
||||
else:
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if sorted_process_parameters[i] in consecutive_process_parameters:
|
||||
is_purchase = True
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
# 当前面的连续外协采购单生成再生成当前外协采购单
|
||||
if is_purchase is False:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if sorted_process_parameters[i] in consecutive_process_parameters:
|
||||
is_purchase = True
|
||||
consecutive_process_parameters = []
|
||||
m = 0
|
||||
if m == len(consecutive_process_parameters) - 1 and m != 0:
|
||||
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
if is_purchase is False and m == 0:
|
||||
if len(sorted_process_parameters) == 1:
|
||||
self.env['purchase.order'].get_purchase_order(sorted_process_parameters,
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
else:
|
||||
self.env['purchase.order'].get_purchase_order(sorted_process_parameters[i],
|
||||
production_item,
|
||||
product_id_to_production_names)
|
||||
# # 同一个产品多个制造订单对应一个编程单和模型库
|
||||
# # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递
|
||||
if not production_item.programming_no:
|
||||
production_programming = self.env['mrp.production'].search(
|
||||
[('product_id.id', '=', production_item.product_id.id),
|
||||
('origin', '=', production_item.origin)],
|
||||
limit=1, order='id asc')
|
||||
if not production_programming.programming_no:
|
||||
production_item.fetchCNC(
|
||||
', '.join(product_id_to_production_names[production_item.product_id.id]))
|
||||
@@ -368,7 +447,7 @@ class ProductionLot(models.Model):
|
||||
if product.tracking == "serial":
|
||||
last_serial = self.env['stock.lot'].search(
|
||||
[('company_id', '=', company.id), ('product_id', '=', product.id)],
|
||||
limit=1, order='id DESC')
|
||||
limit=1, order='name desc')
|
||||
if last_serial:
|
||||
if product.categ_id.name == '刀具':
|
||||
return self.env['stock.lot'].get_tool_generate_lot_names1(company, product)
|
||||
@@ -468,12 +547,11 @@ class ProductionLot(models.Model):
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
# workorder_in_id = fields.One2many('mrp.workorder', 'picking_in_id')
|
||||
# workorder_out_id = fields.One2many('mrp.workorder', 'picking_out_id')
|
||||
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数")
|
||||
|
||||
# 设置外协出入单的名称
|
||||
def _get_name_Res(self, rescode):
|
||||
last_picking = self.sudo().search([('name', 'like', rescode)], order='create_date desc,id desc', limit=1)
|
||||
last_picking = self.sudo().search([('name', 'ilike', rescode)], order='create_date desc,id desc', limit=1)
|
||||
if not last_picking:
|
||||
num = "%04d" % 1
|
||||
else:
|
||||
@@ -499,9 +577,10 @@ class StockPicking(models.Model):
|
||||
[('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
|
||||
('location_id', '=', self.env['stock.location'].search(
|
||||
[('barcode', 'ilike', 'VL-SPOC')]).id),
|
||||
('origin', '=', self.origin)])
|
||||
('origin', '=', self.origin), ('picking_id', '=', self.id)])
|
||||
if self.location_id == move_in.location_id and self.location_dest_id == move_in.location_dest_id:
|
||||
if move_out.origin == move_in.origin:
|
||||
move_in.write({'production_id': False})
|
||||
if move_out.picking_id.state != 'done':
|
||||
raise UserError(
|
||||
_('该入库单对应的单号为%s的出库单还未完成,不能进行验证操作!' % move_out.picking_id.name))
|
||||
@@ -516,7 +595,7 @@ class StockPicking(models.Model):
|
||||
[('barcode', 'ilike', 'VL-SPOC')]).id),
|
||||
('origin', '=', self.origin)])
|
||||
production = self.env['mrp.production'].search([('name', '=', self.origin)])
|
||||
if move_in:
|
||||
if move_in.state != 'done':
|
||||
move_in.write({'state': 'assigned'})
|
||||
self.env['stock.move.line'].create(move_in.get_move_line(production, None))
|
||||
|
||||
@@ -526,7 +605,7 @@ class StockPicking(models.Model):
|
||||
def create_outcontract_picking(self, sorted_workorders_arr, item):
|
||||
m = 0
|
||||
for sorted_workorders in sorted_workorders_arr:
|
||||
pick_ids = []
|
||||
# pick_ids = []
|
||||
if m == 0:
|
||||
outcontract_stock_move = self.env['stock.move'].search(
|
||||
[('workorder_id', '=', sorted_workorders.id), ('production_id', '=', item.id)])
|
||||
@@ -545,7 +624,7 @@ class StockPicking(models.Model):
|
||||
outcontract_picking_type_out))
|
||||
picking_out = self.create(
|
||||
moves_out._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCOUT/'))
|
||||
pick_ids.append(picking_out.id)
|
||||
# pick_ids.append(picking_out.id)
|
||||
moves_out.write(
|
||||
{'picking_id': picking_out.id, 'state': 'waiting', 'workorder_id': sorted_workorders.id})
|
||||
moves_out._assign_picking_post_process(new=new_picking)
|
||||
@@ -554,12 +633,12 @@ class StockPicking(models.Model):
|
||||
outcontract_picking_type_in))
|
||||
picking_in = self.create(
|
||||
moves_in._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCIN/'))
|
||||
pick_ids.append(picking_in.id)
|
||||
# pick_ids.append(picking_in.id)
|
||||
moves_in.write(
|
||||
{'picking_id': picking_in.id, 'state': 'waiting', 'workorder_id': sorted_workorders.id})
|
||||
moves_in._assign_picking_post_process(new=new_picking)
|
||||
m += 1
|
||||
sorted_workorders.write({'picking_ids': [(6, 0, pick_ids)]})
|
||||
# sorted_workorders.write({'picking_ids': [(6, 0, pick_ids)]})
|
||||
|
||||
|
||||
class ReStockMove(models.Model):
|
||||
@@ -587,14 +666,20 @@ class ReStockMove(models.Model):
|
||||
return move_values
|
||||
|
||||
def _get_new_picking_values_Res(self, item, sorted_workorders, rescode):
|
||||
picking_type_id = self.mapped('picking_type_id').id
|
||||
if rescode == 'WH/OCOUT/':
|
||||
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_out').id
|
||||
elif rescode == 'WH/OCIN/':
|
||||
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_in').id
|
||||
return {
|
||||
'name': self.env['stock.picking']._get_name_Res(rescode),
|
||||
'origin': item.name,
|
||||
'surface_technics_parameters_id': sorted_workorders.surface_technics_parameters_id.id,
|
||||
'company_id': self.mapped('company_id').id,
|
||||
'user_id': False,
|
||||
'move_type': self.mapped('group_id').move_type or 'direct',
|
||||
'partner_id': sorted_workorders.supplier_id.id,
|
||||
'picking_type_id': self.mapped('picking_type_id').id,
|
||||
'picking_type_id': picking_type_id,
|
||||
'location_id': self.mapped('location_id').id,
|
||||
'location_dest_id': self.mapped('location_dest_id').id,
|
||||
'state': 'confirmed',
|
||||
|
||||
@@ -150,5 +150,12 @@ access_sf_processing_panel_group_sf_order_user,sf_processing_panel_group_sf_orde
|
||||
access_sf_production_wizard_group_sf_order_user,sf_production_wizard_group_sf_order_user,model_sf_production_wizard,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_sf_processing_panel_group_plan_dispatch,sf_processing_panel_group_plan_dispatch,model_sf_processing_panel,sf_base.group_plan_dispatch,1,1,1,0
|
||||
|
||||
access_sf_agv_scheduling_admin,sf_agv_scheduling_admin,model_sf_agv_scheduling,base.group_system,1,1,1,1
|
||||
access_sf_agv_scheduling_group_sf_order_user,sf_agv_scheduling_group_sf_order_user,model_sf_agv_scheduling,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_sf_agv_scheduling_group_sf_mrp_manager,sf_agv_scheduling_group_sf_mrp_manager,model_sf_agv_scheduling,sf_base.group_sf_mrp_manager,1,1,1,0
|
||||
access_sf_agv_scheduling_group_sf_equipment_user,sf_agv_scheduling_group_sf_equipment_user,model_sf_agv_scheduling,sf_base.group_sf_equipment_user,1,1,1,0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -1,16 +1,36 @@
|
||||
var RFID = ''
|
||||
$(document).off('keydown')
|
||||
console.log(2222)
|
||||
$(document).on('keydown', '.modal.d-block.o_technical_modal,body.o_web_client', function (e) {
|
||||
const dom = $('.customRFID')
|
||||
if(!dom.length) return
|
||||
$(document).on('keydown', 'body.o_web_client', function (e) {
|
||||
setTimeout(() => {
|
||||
RFID = ''
|
||||
|
||||
}, 200)
|
||||
if(e.key == 'Enter' && e.keyCode == 13 || e.key == 'Tab' && e.keyCode == 9){
|
||||
if(!RFID || RFID.length <= 3) return;
|
||||
dom.children('span').text(RFID)
|
||||
RFID = ''
|
||||
|
||||
let fieldValue1 = $('[name="routing_type"]');
|
||||
console.log('字段值:', fieldValue1.text());
|
||||
console.log(RFID)
|
||||
let fieldValue2 = $('[name="rfid_code"]');
|
||||
console.log('字段值2:', fieldValue2.text());
|
||||
// if(!RFID || RFID.length <= 3) return;
|
||||
// $('[name="button_start"]').trigger('click')
|
||||
// setTimeout(() => {
|
||||
// $('.o_dialog .modal-footer .btn-primary').trigger('click')
|
||||
// }, 50)
|
||||
// RFID = ''
|
||||
// return;
|
||||
|
||||
// fieldValue2.val() === '')
|
||||
// 检查字段值是否等于“装夹预调”
|
||||
if (fieldValue1.text() === '装夹预调') {
|
||||
if (!RFID || RFID.length <= 3) return;
|
||||
$('[name="button_start"]').trigger('click');
|
||||
setTimeout(() => {
|
||||
$('.o_dialog .modal-footer .btn-primary').trigger('click');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
RFID = '';
|
||||
return;
|
||||
}
|
||||
RFID += e.key
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
odoo.define('sf_manufacturing.action_dispatch_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 dispatch_confirm(parent, {params}) {
|
||||
const dialog = new Dialog(parent, {
|
||||
title: "确认",
|
||||
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
|
||||
buttons: [
|
||||
{ text: "确认", classes: 'btn-primary jikimo_button_confirm', close: true, click: () => dispatchConfirmed(parent, params) },
|
||||
{ text: "取消", close: true },
|
||||
],
|
||||
});
|
||||
dialog.open();
|
||||
|
||||
|
||||
async function dispatchConfirmed(parent, params) {
|
||||
console.log(parent, 'parent')
|
||||
rpc.query({
|
||||
model: 'sf.workpiece.delivery.wizard',
|
||||
method: 'confirm',
|
||||
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': '任务下发成功!AGV任务调度编号为【' + res.name + '】',
|
||||
'type': 'success',
|
||||
'sticky': false,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
core.action_registry.add('dispatch_confirm', dispatch_confirm);
|
||||
return dispatch_confirm;
|
||||
});
|
||||
57
sf_manufacturing/static/src/xml/button_show_on_tree.xml
Normal file
57
sf_manufacturing/static/src/xml/button_show_on_tree.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="sf_manufacturing.button_show" t-inherit="web.ListView.Buttons" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div/t[@t-if='nbSelected']" position="after">
|
||||
<t t-elif="!nbSelected">
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<t t-if="button.modifiers.force_show">
|
||||
<ListViewHeaderButton
|
||||
list="model.root"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="button.defaultRank"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
className="button.className+' ms-2'"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div/t[@t-if='nbSelected']" position="replace">
|
||||
<t t-if="nbSelected">
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<t t-if="!button.modifiers.force_show">
|
||||
<ListViewHeaderButton
|
||||
list="model.root"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="button.defaultRank"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="!env.isSmall">
|
||||
<t t-call="web.ListView.Selection"/>
|
||||
</t>
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<t t-if="button.modifiers.force_show == 1">
|
||||
<ListViewHeaderButton
|
||||
list="model.root"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="button.defaultRank"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
className="button.className+' ms-2'"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
79
sf_manufacturing/views/agv_scheduling_views.xml
Normal file
79
sf_manufacturing/views/agv_scheduling_views.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- agv站点 -->
|
||||
<record id="view_agv_scheduling_tree" model="ir.ui.view">
|
||||
<field name="name">agv调度</field>
|
||||
<field name="model">sf.agv.scheduling</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom" delete="0" create="0">
|
||||
<field name="state" widget="badge"
|
||||
decoration-success="state == '已配送'"
|
||||
decoration-warning="state == '待下发'"
|
||||
decoration-danger="state == '配送中'"
|
||||
decoration-info="state == '已取消'"
|
||||
/>
|
||||
<field name="agv_route_type" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="agv_route_id"/>
|
||||
<field name="start_site_id"/>
|
||||
<field name="end_site_id"/>
|
||||
<field name="site_state"/>
|
||||
<field name="delivery_workpieces"/>
|
||||
<field name="task_create_time" readonly="1"/>
|
||||
<field name="task_delivery_time" readonly="1"/>
|
||||
<field name="task_completion_time" readonly="1"/>
|
||||
<field name="task_duration" readonly="1"/>
|
||||
|
||||
<button
|
||||
name="button_cancel"
|
||||
string="取消" type="object"
|
||||
attrs="{'invisible': ['|', ('state', '!=', '待下发'), ('agv_route_type', '=', '运送空料架')]}"
|
||||
icon="fa-times"
|
||||
class="btn-danger"
|
||||
confirm="你确定要取消这条记录吗?"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_agv_scheduling_search" model="ir.ui.view">
|
||||
<field name="name">sf.agv.scheduling.search</field>
|
||||
<field name="model">sf.agv.scheduling</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="AGV调度">
|
||||
<field name="name"/>
|
||||
<field name="agv_route_id"/>
|
||||
<field name="start_site_id"/>
|
||||
<field name="end_site_id"/>
|
||||
<field name="delivery_workpieces"/>
|
||||
<field name="state" string="状态"/>
|
||||
<filter name="filter_to_be_issued" string="待下发" domain="[('state', 'in', ['待下发'])]"/>
|
||||
<filter name="filter_delivering" string="配送中" domain="[('state', 'in', ['配送中'])]"/>
|
||||
<filter name="filter_delivered" string="已配送" domain="[('state', 'in', ['已配送'])]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_agv_scheduling_tree" model="ir.actions.act_window">
|
||||
<field name="name">AGV调度</field>
|
||||
<field name="res_model">sf.agv.scheduling</field>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="context">
|
||||
{
|
||||
"search_default_filter_to_be_issued": 1,
|
||||
"search_default_filter_delivering": 1,
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_action_agv_scheduling"
|
||||
name="AGV调度"
|
||||
sequence="28"
|
||||
action="action_agv_scheduling_tree"
|
||||
parent="mrp.menu_mrp_manufacturing"
|
||||
groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"
|
||||
/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -8,7 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom">
|
||||
<field name="name" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="owning_region" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="workcenter_id" required="1" options="{'no_create': True}"/>
|
||||
<field name="state" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="divide_the_work" required="1"/>
|
||||
</tree>
|
||||
@@ -40,8 +40,9 @@
|
||||
<field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站"
|
||||
attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="end_site_id" required="1" options="{'no_create': True}" string="终点接驳站"/>
|
||||
<field name="destination_production_line_id" required="1" options="{'no_create': True}"
|
||||
attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<!-- <field name="destination_production_line_id" required="1" options="{'no_create': True}"-->
|
||||
<!-- attrs="{'readonly': [('id', '!=', False)]}"/>-->
|
||||
<field name="workcenter_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <xpath expr="//button[@name='do_unreserve']" position="after">-->
|
||||
<!-- <button name="do_update_program" type="object" string="更新程序"-->
|
||||
<!-- groups="sf_base.group_sf_mrp_user"/>-->
|
||||
<!-- </xpath>-->
|
||||
<!-- <xpath expr="//button[@name='do_unreserve']" position="after">-->
|
||||
<!-- <button name="do_update_program" type="object" string="更新程序"-->
|
||||
<!-- groups="sf_base.group_sf_mrp_user"/>-->
|
||||
<!-- </xpath>-->
|
||||
<xpath expr="//field[@name='product_id']" position="replace"/>
|
||||
<xpath expr="//field[@name='product_qty']" position="replace"/>
|
||||
<xpath expr="//field[@name='product_uom_id']" position="replace"/>
|
||||
@@ -70,7 +70,7 @@
|
||||
<!-- <attribute name="statusbar_visible">draft,confirmed,progress,pending_processing,completed,done -->
|
||||
<!-- </attribute> -->
|
||||
<attribute name="statusbar_visible">
|
||||
confirmed,pending_cam,progress,done
|
||||
confirmed,pending_cam,progress,rework,scrap,done
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet//group//group[2]//label" position="before">
|
||||
@@ -127,10 +127,10 @@
|
||||
confirm="是否确认更新程序"
|
||||
attrs="{'invisible': ['|',('state', '!=', 'rework'),('programming_state', '!=', '已编程未下发')]}"/>
|
||||
<button name="button_rework" string="返工" type="object" groups="sf_base.group_sf_mrp_user"
|
||||
attrs="{'invisible': ['|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程')]}"/>
|
||||
<!-- <button name="%(sf_manufacturing.action_sf_production_wizard)d" string="报废" type="action"-->
|
||||
<!-- groups="sf_base.group_sf_mrp_user"-->
|
||||
<!-- attrs="{'invisible': [('is_scrap', '=', False)]}"/>-->
|
||||
attrs="{'invisible': ['|','|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程'),('is_rework', '=', True)]}"/>
|
||||
<button name="button_scrap_new" string="报废" type="object"
|
||||
groups="sf_base.group_sf_mrp_user"
|
||||
attrs="{'invisible': ['|',('is_scrap', '=', False),('state','=','cancel')]}"/>
|
||||
</xpath>
|
||||
<xpath expr="(//header//button[@name='button_mark_done'])[3]" position="replace">
|
||||
<button name="button_mark_done" attrs="{'invisible': [
|
||||
@@ -201,6 +201,19 @@
|
||||
data-hotkey="l"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[@name='action_view_mo_delivery']" position="before">
|
||||
<button class="oe_stat_button" name="action_view_remanufacture_productions" type="object"
|
||||
icon="fa-wrench" attrs="{'invisible': [('remanufacture_count', '=', 0)]}"
|
||||
groups="mrp.group_mrp_user">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="remanufacture_count"/>
|
||||
</span>
|
||||
<span class="o_stat_text">新的制造</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//header//button[@name='action_toggle_is_locked']" position="replace">
|
||||
<button name="action_toggle_is_locked"
|
||||
attrs="{'invisible': ['|', ('show_lock', '=', False), ('is_locked', '=', True)]}"
|
||||
@@ -285,7 +298,7 @@
|
||||
|
||||
<xpath expr="//sheet//notebook//page[@name='operations']" position="after">
|
||||
<page string="检测结果" attrs="{'invisible': [('detection_result_ids', '=', [])]}">
|
||||
<field name="detection_result_ids" string="" readonly="1">
|
||||
<field name="detection_result_ids" string="" readonly="0">
|
||||
<tree sample="1">
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="processing_panel"/>
|
||||
@@ -424,6 +437,12 @@
|
||||
<xpath expr="//header//button[@name='action_cancel']" position="replace">
|
||||
<button name="action_cancel" type="object" string="取消" groups="sf_base.group_sf_mrp_user"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="replace">
|
||||
<field name="state" decoration-success="state in ('done', 'to_close')"
|
||||
decoration-warning="state == 'progress'" decoration-info="state == 'confirmed'"
|
||||
decoration-danger="state in ('cancel','rework','scrap')" decoration-muted="state == 'draft'"
|
||||
optional="show" widget="badge" class="text-dark"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="after">
|
||||
<field name="tool_state" invisible="1"/>
|
||||
</xpath>
|
||||
@@ -460,6 +479,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='filter_in_progress']" position="before">
|
||||
<filter string="返工" name="filter_rework" domain="[('state', '=', 'rework')]"/>
|
||||
<filter string="报废" name="filter_scrap" domain="[('state', '=', 'scrap')]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='planning_issues']" position="before">
|
||||
<separator/>
|
||||
@@ -568,7 +588,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<!-- <field name="handle_result"/>-->
|
||||
<field name="handle_result"/>
|
||||
<field name="test_report" readonly="1" widget="pdf_viewer"/>
|
||||
</group>
|
||||
</form>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<field name="bom_product_template_attribute_value_ids" position="after">
|
||||
<field name="routing_type" required="1"/>
|
||||
<field name="is_repeat"/>
|
||||
<field name="reserved_duration"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<div name="button_box" position="inside">
|
||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||
@@ -25,38 +25,81 @@
|
||||
</record>
|
||||
|
||||
<record id="custom_model_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">custom.model.form.view.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//form//sheet' position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<field name="name">custom.model.form.view.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//form//sheet' position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="after">
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Desktop view -->
|
||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
||||
<field name="equipment_status" />
|
||||
<field name="equipment_image" />
|
||||
</xpath>
|
||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
||||
<field name="equipment_status" />
|
||||
<field name="equipment_image" widget="image" />
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<label for="default_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="default_capacity" string="产线日产能"/>
|
||||
件
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="before">
|
||||
<label for="available_machine_number"/>
|
||||
<div class="o_row">
|
||||
<field name="available_machine_number"/>
|
||||
台
|
||||
</div>
|
||||
<label for="single_machine_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="single_machine_capacity"/>
|
||||
件/小时
|
||||
</div>
|
||||
<label for="production_line_hour_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="production_line_hour_capacity"/>
|
||||
件/小时
|
||||
</div>
|
||||
<label for="effective_working_hours_day"/>
|
||||
<div class="o_row">
|
||||
<field name="effective_working_hours_day"/>
|
||||
小时
|
||||
</div>
|
||||
</xpath>
|
||||
<!-- <xpath expr='//group[@name="capacity"]//field[@name="default_capacity"])' position="after">-->
|
||||
<!-- <group>-->
|
||||
<!-- <field name="available_machine_number"/>-->
|
||||
<!-- <field name="single_machine_capacity"/>-->
|
||||
<!-- <field name="production_line_hour_capacity"/>-->
|
||||
<!-- <field name="effective_working_hours_day"/>-->
|
||||
|
||||
<!-- </group>-->
|
||||
<!-- </xpath>-->
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Desktop view -->
|
||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
||||
<field name="equipment_status"/>
|
||||
<field name="equipment_image"/>
|
||||
</xpath>
|
||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
||||
<field name="equipment_status"/>
|
||||
<field name="equipment_image" widget="image"/>
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
@@ -73,11 +116,11 @@
|
||||
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<!-------------------->
|
||||
<!-------------------->
|
||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
@@ -99,7 +142,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 继承原有的看板视图 -->
|
||||
<!-- 继承原有的看板视图 -->
|
||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
@@ -182,6 +225,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='resource_calendar_id']" position="after">
|
||||
<field name="is_process_outsourcing"/>
|
||||
<field name="is_agv_scheduling"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -390,9 +434,9 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<div name="button_box" position="inside">
|
||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<field name="name" decoration-success="is_subcontract" decoration-bf="is_subcontract"/>
|
||||
</field>
|
||||
<field name="name" position="before">
|
||||
<field name="sequence"/>
|
||||
<field name="sequence" string="序号"/>
|
||||
<field name='user_permissions' invisible="1"/>
|
||||
</field>
|
||||
<field name="name" position="after">
|
||||
@@ -32,18 +32,28 @@
|
||||
</field>
|
||||
<xpath expr="//field[@name='qty_remaining']" position="after">
|
||||
<field name="manual_quotation" optional="show"/>
|
||||
<field name='tag_type' widget="badge"
|
||||
decoration-danger="tag_type == '重新加工'"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_planned_start']" position="replace">
|
||||
<field name="date_planned_start" string="计划开始日期" optional="show"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_planned_start']" position="before">
|
||||
<field name="reserved_duration" string="计划预留时间" optional="hide"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_planned_finished']" position="replace">
|
||||
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='button_start']" position="attributes">
|
||||
<!-- <attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',-->
|
||||
<!-- 'done',-->
|
||||
<!-- 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),-->
|
||||
<!-- ('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}-->
|
||||
<!-- </attribute>-->
|
||||
<attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',
|
||||
'done',
|
||||
'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),
|
||||
('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}
|
||||
('is_user_working', '!=', False),("user_permissions","=",False),("name","in",("CNC加工","解除装夹"))]}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='%(mrp.act_mrp_block_workcenter_wo)d']" position="attributes">
|
||||
@@ -113,11 +123,25 @@
|
||||
<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="//form" position="inside">
|
||||
<script src="sf_manufacturing/static/src/js/customRFID.js"/>
|
||||
</xpath>
|
||||
<xpath expr="//header/field[@name='state']" position="replace">
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
|
||||
icon="fa-credit-card"
|
||||
groups="base.group_user,sf_base.group_sf_order_user"
|
||||
attrs="{'invisible': [('surface_technics_purchase_count', '=', 0),('routing_type', '!=', '表面工艺')]}">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="surface_technics_purchase_count"/>
|
||||
</span>
|
||||
<span class="o_stat_text">采购</span>
|
||||
</div>
|
||||
</button>
|
||||
<button type="object" name="action_view_surface_technics_picking" class="oe_stat_button" icon="fa-truck"
|
||||
groups="base.group_user,sf_base.group_sf_order_user"
|
||||
attrs="{'invisible': [('surface_technics_picking_count', '=', 0)]}">
|
||||
@@ -130,6 +154,7 @@
|
||||
<field name='name' invisible="1"/>
|
||||
<field name='is_rework' invisible="1"/>
|
||||
<field name='is_delivery' invisible="1"/>
|
||||
<field name="is_trayed" invisible="1"/>
|
||||
<!-- <field name='is_send_program_again' invisible="1"/>-->
|
||||
<!-- 工单form页面的开始停工按钮等 -->
|
||||
<!-- <button name="button_start" type="object" string="开始" class="btn-success" -->
|
||||
@@ -142,8 +167,12 @@
|
||||
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'not in', ('pending_processing', 'pending_cam', 'pending_era_cam')), ('state','!=','progress'), ('routing_type', 'not in', ('装夹预调', 'CNC加工', '解除装夹'))]}" -->
|
||||
<!-- groups="sf_base.group_sf_mrp_user" confirm="是否确认完工"/> -->
|
||||
|
||||
<!-- <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"-->
|
||||
<!-- attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>-->
|
||||
<button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"
|
||||
attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
|
||||
attrs="{'invisible': ['|', '|', '|', '|', '|', ('routing_type', '=', '装夹预调'), ('routing_type', '=', '解除装夹'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
|
||||
<button name="button_start" type="object" string="开始" class="btn-success"
|
||||
attrs="{'invisible': ['|', '|', '|', '|', ('routing_type', '!=', '装夹预调'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
|
||||
<button name="button_pending" type="object" string="暂停" class="btn-warning"
|
||||
attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
|
||||
<button name="button_finish" type="object" string="完成" class="btn-success" confirm="是否确认完工"
|
||||
@@ -164,11 +193,14 @@
|
||||
<!-- context="{'default_workcenter_id': workcenter_id}" class="btn-danger" -->
|
||||
<!-- groups="sf_base.group_sf_mrp_user" -->
|
||||
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '!=', 'blocked'),('state','=','done')]}"/> -->
|
||||
<button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"
|
||||
attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&',('rfid_code','in',['',False]),('state','=','done')]}"/>
|
||||
<!-- <button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"-->
|
||||
<!-- attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&',('rfid_code','in',['',False]),('state','=','done')]}"/>-->
|
||||
<button name="button_rework_pre" type="object" string="返工"
|
||||
class="btn-primary"
|
||||
attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('state','!=','progress'),('is_rework','=',True)]}"/>
|
||||
<button name="unbind_tray" type="object" string="解绑托盘"
|
||||
class="btn-primary"
|
||||
attrs="{'invisible': ['|', '|', '|', ('routing_type','!=','装夹预调'),('state','!=','progress'), ('is_trayed', '=', False), ('state', 'in', ('done'))]}"/>
|
||||
<button name="print_method" type="object" string="打印二维码" class="btn-primary"
|
||||
attrs="{'invisible': ['|',('routing_type','!=','解除装夹'),('state','!=','done')]}"/>
|
||||
</xpath>
|
||||
@@ -191,9 +223,12 @@
|
||||
<xpath expr="//label[1]" position="before">
|
||||
<field name='routing_type' readonly="1"/>
|
||||
<field name='process_state' attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
|
||||
<field name='tag_type' readonly="1" attrs='{"invisible": [("tag_type","=",False)]}'
|
||||
decoration-danger="tag_type == '重新加工'"/>
|
||||
<field name="rfid_code" force_save="1" readonly="1" cache="True"
|
||||
attrs="{'invisible': [('rfid_code_old', '!=', False)]}"/>
|
||||
<field name="rfid_code_old" readonly="1" attrs="{'invisible': [('rfid_code_old', '=', False)]}"/>
|
||||
|
||||
</xpath>
|
||||
<xpath expr="//label[1]" position="attributes">
|
||||
<attribute name="string">计划加工时间</attribute>
|
||||
@@ -449,10 +484,10 @@
|
||||
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<field name="data_state" invisible="1"/>
|
||||
<button type="object" class="oe_highlight" name="get_three_check_datas" string="获取数据"
|
||||
attrs='{"invisible": ["|", "|", "|", ("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", True)]}'/>
|
||||
<button type="object" class="oe_highlight" name="getcenter" string="计算定位"
|
||||
attrs='{"invisible": ["|","|", "|",("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", False)]}'/>
|
||||
<!-- <button type="object" class="oe_highlight" name="get_three_check_datas" string="获取数据" -->
|
||||
<!-- attrs='{"invisible": ["|", "|", "|", ("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", True)]}'/> -->
|
||||
<!-- <button type="object" class="oe_highlight" name="getcenter" string="计算定位" -->
|
||||
<!-- attrs='{"invisible": ["|","|", "|",("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", False)]}'/> -->
|
||||
</div>
|
||||
|
||||
<group>
|
||||
@@ -483,6 +518,11 @@
|
||||
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//form//header" position="inside">
|
||||
<button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas"
|
||||
string="获取数据" attrs='{"invisible": [("state","!=","progress")]}'/>
|
||||
</xpath>
|
||||
|
||||
|
||||
<xpath expr="//page[1]" position="before">
|
||||
<field name="results" invisible="1"/>
|
||||
@@ -511,7 +551,7 @@
|
||||
<xpath expr="//page[1]" position="before">
|
||||
<page string="CNC程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
|
||||
<field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id"
|
||||
readonly="1">
|
||||
readonly="0">
|
||||
<tree>
|
||||
<field name="sequence_number"/>
|
||||
<field name="program_name"/>
|
||||
@@ -629,29 +669,30 @@
|
||||
<field name="name">工件配送</field>
|
||||
<field name="model">sf.workpiece.delivery</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="工件配送" class="center" create="0" delete="0">
|
||||
<tree string="工件配送" class="center" create="0" delete="0" js_class="remove_focus_list_view">
|
||||
<header>
|
||||
<button name="button_delivery" type="object" string="配送" class="oe_highlight"/>
|
||||
<button name="button_delivery" type="object" string="工件配送"
|
||||
class="btn-primary jikimo_button_confirm" attrs="{'force_show':1}"/>
|
||||
</header>
|
||||
<field name="status" widget="badge"
|
||||
decoration-success="status == '已配送'"
|
||||
decoration-warning="status == '待下发'"
|
||||
decoration-danger="status == '待配送'"
|
||||
decoration-danger="status == '已下发'"
|
||||
decoration-info="status == '已取消'"
|
||||
/>
|
||||
<field name="name"/>
|
||||
<field name="production_id"/>
|
||||
<field name="type" readonly="1"/>
|
||||
<field name="production_line_id" options="{'no_create': True}" readonly="1"/>
|
||||
<field name="route_id" options="{'no_create': True}"
|
||||
domain="[('route_type','in',['上产线','下产线'])]"/>
|
||||
<!-- <field name="route_id" options="{'no_create': True}"-->
|
||||
<!-- domain="[('route_type','in',['上产线','下产线'])]"/>-->
|
||||
<field name="feeder_station_start_id" readonly="1" force_save="1"/>
|
||||
<field name="feeder_station_destination_id" readonly="1" force_save="1"/>
|
||||
<!-- <field name="feeder_station_destination_id" readonly="1" force_save="1"/>-->
|
||||
<field name="is_cnc_program_down" readonly="1"/>
|
||||
<!-- <field name="rfid_code"/>-->
|
||||
<field name="task_delivery_time" readonly="1"/>
|
||||
<field name="task_completion_time" readonly="1"/>
|
||||
<field name="delivery_duration" widget="float_time"/>
|
||||
<!-- <field name="task_delivery_time" readonly="1"/>-->
|
||||
<!-- <field name="task_completion_time" readonly="1"/>-->
|
||||
<!-- <field name="delivery_duration" widget="float_time"/>-->
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -706,7 +747,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="工件配送">
|
||||
<filter name="filter_to_be_issued" string="待下发" domain="[('status', 'in', ['待下发'])]"/>
|
||||
<filter name="filter_waiting_delivery" string="待配送" domain="[('status', 'in', ['待配送'])]"/>
|
||||
<filter name="filter_issued" string="已下发" domain="[('status', 'in', ['已下发'])]"/>
|
||||
<filter name="filter_delivered" string="已配送" domain="[('status', 'in', ['已配送'])]"/>
|
||||
<field name="rfid_code"/>
|
||||
<field name="production_id"/>
|
||||
@@ -730,7 +771,7 @@
|
||||
<field name="res_model">sf.workpiece.delivery</field>
|
||||
<field name="search_view_id" ref="sf_workpiece_delivery_search"/>
|
||||
<field name="context">{'search_default_filter_to_be_issued': 1,
|
||||
'search_default_filter_waiting_delivery': 1}
|
||||
'search_default_filter_issued': 1}
|
||||
</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">
|
||||
@@ -817,5 +858,11 @@
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="domain">[('type','in',['运送空料架']),('name','not ilike','WDO')]</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="mrp.menu_mrp_manufacturing"
|
||||
name="Operations"
|
||||
sequence="10"
|
||||
parent="mrp.menu_mrp_root"
|
||||
groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"/>
|
||||
</odoo>
|
||||
|
||||
|
||||
50
sf_manufacturing/views/res_config_settings_views.xml
Normal file
50
sf_manufacturing/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_view_form_sf_sync" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.sf_sync</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
|
||||
<div>
|
||||
<h2>AGV参数配置</h2>
|
||||
<div class="row mt16 o_settings_container" id="agv_config">
|
||||
<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="agv_rcs_url" string="访问地址"/>
|
||||
<field name="agv_rcs_url"/>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<label for="agv_code" string="车辆编号"/>
|
||||
<field name="agv_code"/>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
<label for="wbcode"/>
|
||||
<field name="wbcode"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
|
||||
<xpath expr="//div[@id='agv_config']/div" position="after">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="is_agv_task_dispatch"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<div class="text-muted">
|
||||
<label for="is_agv_task_dispatch"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -2,6 +2,8 @@
|
||||
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo.addons.stock.models.stock_rule import ProcurementException
|
||||
from datetime import datetime
|
||||
from odoo import models, api, fields, _
|
||||
|
||||
@@ -11,11 +13,91 @@ class ProductionWizard(models.TransientModel):
|
||||
_description = '制造订单向导'
|
||||
|
||||
production_id = fields.Many2one('mrp.production', string='制造订单号')
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=True)
|
||||
reprogramming_num = fields.Integer('重新编程次数', default=0)
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
|
||||
is_remanufacture = fields.Boolean(string='重新生成制造订单', default=True)
|
||||
programming_states = fields.Selection(
|
||||
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
|
||||
('已下发', '已下发')],
|
||||
string='编程状态')
|
||||
|
||||
@api.onchange('is_remanufacture')
|
||||
def _onchange_is_reprogramming(self):
|
||||
if self.is_remanufacture is False:
|
||||
self.is_reprogramming = False
|
||||
|
||||
def confirm(self):
|
||||
if self.is_reprogramming is True:
|
||||
self.production_id.update_programming_state()
|
||||
self.production_id.action_cancel()
|
||||
self.production_id.detection_result_ids.write({'handle_result': '已处理'})
|
||||
self.production_id.write({'state': 'cancel', 'scrap_ids': [(0, 0, {
|
||||
'name': self.env['ir.sequence'].next_by_code('stock.scrap') or _('New'),
|
||||
'product_id': self.production_id.product_id.id,
|
||||
'scrap_qty': 1,
|
||||
'origin': self.production_id.origin,
|
||||
'date_done': fields.datetime.now(),
|
||||
'lot_id': self.env['stock.move.line'].search(
|
||||
[('move_id', '=', self.production_id.move_raw_ids[0].id)]).lot_id.id,
|
||||
'location_id': self.production_id.move_raw_ids.filtered(lambda x: x.state not in (
|
||||
'done',
|
||||
'cancel')) and self.production_id.location_src_id.id or self.production_id.location_dest_id.id,
|
||||
'scrap_location_id': self.env['stock.scrap']._get_default_scrap_location_id(),
|
||||
'state': 'done'})]})
|
||||
self.production_id.action_cancel()
|
||||
if self.is_remanufacture is True:
|
||||
ret = {'programming_list': [], 'is_reprogramming': self.is_reprogramming}
|
||||
if self.is_reprogramming is True:
|
||||
self.production_id.update_programming_state()
|
||||
else:
|
||||
scrap_cnc = self.production_id.workorder_ids.filtered(lambda crw: crw.routing_type == 'CNC加工').cnc_ids
|
||||
scrap_cmm = self.production_id.workorder_ids.filtered(lambda cm: cm.routing_type == 'CNC加工').cmm_ids
|
||||
for item_line in scrap_cnc:
|
||||
vals = {
|
||||
'sequence_number': item_line.sequence_number,
|
||||
'program_name': item_line.program_name,
|
||||
'cutting_tool_name': item_line.cutting_tool_name,
|
||||
'cutting_tool_no': item_line.cutting_tool_no,
|
||||
'processing_type': item_line.processing_type,
|
||||
'margin_x_y': item_line.margin_x_y,
|
||||
'margin_z': item_line.margin_z,
|
||||
'depth_of_processing_z': item_line.depth_of_processing_z,
|
||||
'cutting_tool_extension_length': item_line.cutting_tool_extension_length,
|
||||
'estimated_processing_time': item_line.estimated_processing_time,
|
||||
'cutting_tool_handle_type': item_line.cutting_tool_handle_type,
|
||||
'ftp_path': item_line.program_path,
|
||||
'processing_panel': item_line.workorder_id.processing_panel,
|
||||
'program_create_date': datetime.strftime(item_line.program_create_date,
|
||||
'%Y-%m-%d %H:%M:%S'),
|
||||
'remark': item_line.remark
|
||||
}
|
||||
ret['programming_list'].append(vals)
|
||||
for cmm_line in scrap_cmm:
|
||||
vals = {
|
||||
'sequence_number': cmm_line.sequence_number,
|
||||
'program_name': cmm_line.program_name,
|
||||
'ftp_path': cmm_line.program_path,
|
||||
'processing_panel': item_line.workorder_id.processing_panel,
|
||||
'program_create_date': datetime.strftime(
|
||||
cmm_line.program_create_date,
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
}
|
||||
ret['programming_list'].append(vals)
|
||||
|
||||
new_production = self.production_id.recreateManufacturing(ret)
|
||||
self.production_id.write({'remanufacture_production_id': new_production.id})
|
||||
if self.is_reprogramming is False:
|
||||
for panel in new_production.product_id.model_processing_panel.split(','):
|
||||
scrap_cnc_workorder = max(
|
||||
self.production_id.workorder_ids.filtered(
|
||||
lambda
|
||||
scn: scn.processing_panel == panel and scn.routing_type == 'CNC加工'),
|
||||
key=lambda w: w.create_date)
|
||||
scrap_pre_workorder = max(self.production_id.workorder_ids.filtered(
|
||||
lambda
|
||||
pr: pr.processing_panel == panel and pr.routing_type == '装夹预调'),
|
||||
key=lambda w1: w1.create_date)
|
||||
new_cnc_workorder = new_production.workorder_ids.filtered(
|
||||
lambda
|
||||
nc: nc.processing_panel == panel and nc.routing_type == 'CNC加工')
|
||||
new_cnc_workorder.write({'cnc_worksheet': scrap_cnc_workorder.cnc_worksheet})
|
||||
new_pre_workorder = new_production.workorder_ids.filtered(lambda
|
||||
p: p.routing_type == '装夹预调' and p.processing_panel == panel)
|
||||
new_pre_workorder.write({'processing_drawing': scrap_pre_workorder.processing_drawing})
|
||||
|
||||
@@ -6,14 +6,28 @@
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="production_id" invisible="True"/>
|
||||
<field name="production_id" invisible="1"/>
|
||||
<field name="programming_states" invisible="1"/>
|
||||
<div>
|
||||
重新生成制造订单
|
||||
<field name="is_remanufacture"/>
|
||||
<field name="is_remanufacture" force_save="1"/>
|
||||
</div>
|
||||
<div>
|
||||
申请重新编程
|
||||
<field name="is_reprogramming" attrs='{"invisible": [("is_remanufacture","=",False)]}'/>
|
||||
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
|
||||
注意: 该制造订单产品已申请重新编程次数为<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>,且当前编程状态为
|
||||
<field name="programming_states" string=""
|
||||
decoration-info="programming_states == '待编程'"
|
||||
decoration-success="programming_states == '已下发'"
|
||||
decoration-warning="programming_states =='编程中'"
|
||||
decoration-danger="programming_states =='已编程'" readonly="1"/>
|
||||
</div>
|
||||
<div attrs='{"invisible": [("is_remanufacture","=",False)]}'>
|
||||
<span style='font-weight:bold;'>申请重新编程
|
||||
<field name="is_reprogramming" force_save="1"
|
||||
attrs='{"readonly": [("programming_states","not in",["已下发"])]}'/>
|
||||
</span>
|
||||
</div>
|
||||
<footer>
|
||||
<button string="确认" name="confirm" type="object" class="oe_highlight" confirm="是否确认报废"/>
|
||||
@@ -28,6 +42,9 @@
|
||||
<field name="name">报废</field>
|
||||
<field name="res_model">sf.production.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<!-- <field name="context">{-->
|
||||
<!-- 'default_production_id': active_id}-->
|
||||
<!-- </field>-->
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ class ReworkWizard(models.TransientModel):
|
||||
processing_panel_id = fields.Many2many('sf.processing.panel', string="加工面")
|
||||
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
|
||||
is_reprogramming_readonly = fields.Boolean(string='申请重新编程(只读)', default=False)
|
||||
is_clamp_measure = fields.Boolean(string='保留装夹测量数据', default=True)
|
||||
reprogramming_num = fields.Integer('重新编程次数', default=0)
|
||||
programming_state = fields.Selection(
|
||||
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
|
||||
@@ -35,6 +36,7 @@ class ReworkWizard(models.TransientModel):
|
||||
|
||||
def confirm(self):
|
||||
if self.routing_type in ['装夹预调', 'CNC加工']:
|
||||
self.is_clamp_measure = False
|
||||
self.workorder_id.is_rework = True
|
||||
self.production_id.write({'detection_result_ids': [(0, 0, {
|
||||
'rework_reason': self.rework_reason,
|
||||
@@ -58,22 +60,18 @@ class ReworkWizard(models.TransientModel):
|
||||
if processing_panels_missing:
|
||||
processing_panels_str = ','.join(processing_panels_missing)
|
||||
raise UserError('您还有待处理的检测结果中为%s的加工面未选择' % processing_panels_str)
|
||||
# processing_panels = set()
|
||||
# for handle_item in handle_result:
|
||||
# for dr_panel in self.processing_panel_id:
|
||||
# if dr_panel.name == handle_item.processing_panel:
|
||||
# processing_panels.add(dr_panel.name)
|
||||
# if len(processing_panels) != len(handle_result):
|
||||
# processing_panels_str = ','.join(processing_panels)
|
||||
# return UserError(f'您还有待处理的检测结果中为{processing_panels_str}的加工面未选择')
|
||||
for panel in self.processing_panel_id:
|
||||
panel_workorder = self.production_id.workorder_ids.filtered(
|
||||
lambda ap: ap.processing_panel == panel.name and ap.state != 'rework')
|
||||
if panel_workorder:
|
||||
panel_workorder.write({'state': 'rework'})
|
||||
panel_workorder.filtered(
|
||||
lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
|
||||
lambda wd: wd.status == '待下发').write({'status': '已取消'})
|
||||
rework_clamp_workorder = max(panel_workorder.filtered(
|
||||
lambda
|
||||
rp: rp.processing_panel == panel.name and rp.routing_type == '装夹预调' and rp.state in [
|
||||
'done', 'rework']))
|
||||
# panel_workorder.filtered(
|
||||
# lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
|
||||
# lambda wd: wd.status == '待下发').write({'status': '已取消'})
|
||||
# workpiece = self.env['sf.workpiece.delivery'].search([('status', '=', '待下发'), (
|
||||
# 'workorder_id', '=',
|
||||
# panel_workorder.filtered(lambda wd: wd.routing_type == '装夹预调').id)])
|
||||
@@ -93,6 +91,43 @@ class ReworkWizard(models.TransientModel):
|
||||
self.production_id.detection_result_ids.filtered(
|
||||
lambda ap1: ap1.processing_panel == panel.name and ap1.handle_result == '待处理').write(
|
||||
{'handle_result': '已处理'})
|
||||
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
|
||||
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
|
||||
'rework', 'done'))
|
||||
if new_pre_workorder and rework_clamp_workorder and self.is_clamp_measure is True:
|
||||
new_pre_workorder.write(
|
||||
{'X1_axis': rework_clamp_workorder.X1_axis, 'Y1_axis': rework_clamp_workorder.Y1_axis
|
||||
, 'Z1_axis': rework_clamp_workorder.Z1_axis,
|
||||
'X2_axis': rework_clamp_workorder.X2_axis
|
||||
, 'Y2_axis': rework_clamp_workorder.Y2_axis,
|
||||
'Z2_axis': rework_clamp_workorder.Z2_axis
|
||||
, 'X3_axis': rework_clamp_workorder.X3_axis,
|
||||
'Y3_axis': rework_clamp_workorder.Y3_axis
|
||||
, 'Z3_axis': rework_clamp_workorder.Z3_axis,
|
||||
'X4_axis': rework_clamp_workorder.X4_axis
|
||||
, 'Y4_axis': rework_clamp_workorder.Y4_axis,
|
||||
'Z4_axis': rework_clamp_workorder.Z4_axis
|
||||
, 'X5_axis': rework_clamp_workorder.X5_axis,
|
||||
'Y5_axis': rework_clamp_workorder.Y5_axis
|
||||
, 'Z5_axis': rework_clamp_workorder.Z5_axis,
|
||||
'X6_axis': rework_clamp_workorder.X6_axis
|
||||
, 'Y6_axis': rework_clamp_workorder.Y6_axis,
|
||||
'Z6_axis': rework_clamp_workorder.Z6_axis
|
||||
, 'X7_axis': rework_clamp_workorder.X7_axis,
|
||||
'Y7_axis': rework_clamp_workorder.Y7_axis
|
||||
, 'Z7_axis': rework_clamp_workorder.Z7_axis,
|
||||
'X8_axis': rework_clamp_workorder.X8_axis
|
||||
, 'Y8_axis': rework_clamp_workorder.Y8_axis,
|
||||
'Z8_axis': rework_clamp_workorder.Z8_axis
|
||||
, 'X9_axis': rework_clamp_workorder.X9_axis,
|
||||
'Y9_axis': rework_clamp_workorder.Y9_axis
|
||||
, 'Z9_axis': rework_clamp_workorder.Z9_axis,
|
||||
'X10_axis': rework_clamp_workorder.X10_axis
|
||||
, 'Y10_axis': rework_clamp_workorder.Y10_axis,
|
||||
'Z10_axis': rework_clamp_workorder.Z10_axis
|
||||
, 'X_deviation_angle': rework_clamp_workorder.X_deviation_angle,
|
||||
'material_center_point': rework_clamp_workorder.material_center_point
|
||||
})
|
||||
if self.is_reprogramming is False:
|
||||
if self.programming_state in ['已编程', '已下发']:
|
||||
if self.reprogramming_num >= 1 and self.programming_state == '已编程':
|
||||
@@ -149,9 +184,7 @@ class ReworkWizard(models.TransientModel):
|
||||
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel.name,
|
||||
ret),
|
||||
'cnc_worksheet': cnc_rework.cnc_worksheet})
|
||||
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
|
||||
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
|
||||
'rework', 'done'))
|
||||
|
||||
if new_pre_workorder:
|
||||
pre_rework = max(self.production_id.workorder_ids.filtered(
|
||||
lambda pr: pr.processing_panel == panel.name and pr.state in (
|
||||
|
||||
@@ -14,17 +14,25 @@
|
||||
<group>
|
||||
<field name="processing_panel_id" options="{'no_create': True}"
|
||||
attrs='{"invisible": [("routing_type","=","装夹预调")]}' widget="many2many_tags"/>
|
||||
|
||||
</group>
|
||||
<div attrs='{"invisible": [("routing_type","=","装夹预调")]}'>
|
||||
<span style='font-weight:bold;'>保留装夹测量数据
|
||||
<field name="is_clamp_measure" force_save="1"/>
|
||||
</span>
|
||||
</div>
|
||||
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
|
||||
注意: 该制造订单的产品已重复编程过<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>次,且当前编程状态为
|
||||
<field name="programming_state" string=""
|
||||
decoration-info="programming_state == '待编程'"
|
||||
decoration-success="programming_state == '已下发'"
|
||||
decoration-warning="programming_state =='编程中'"
|
||||
decoration-danger="programming_state =='已编程'" readonly="1"/>
|
||||
<span style='font-weight:bold;'>
|
||||
注意: 该制造订单产品已申请重新编程次数为<field
|
||||
name="reprogramming_num" string=""
|
||||
readonly="1"
|
||||
style='color:red;'/>,且当前编程状态为
|
||||
<field name="programming_state" string=""
|
||||
decoration-info="programming_state == '待编程'"
|
||||
decoration-success="programming_state == '已下发'"
|
||||
decoration-warning="programming_state =='编程中'"
|
||||
decoration-danger="programming_state =='已编程'" readonly="1"/>
|
||||
</span>
|
||||
</div>
|
||||
<div attrs='{"invisible": ["|",("routing_type","in",["装夹预调","CNC加工"]),("programming_state","not in",["已下发"])],"readonly": [("tool_state", "=", "2")]}'>
|
||||
<span style='font-weight:bold;'>申请重新编程
|
||||
|
||||
@@ -4,30 +4,22 @@
|
||||
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
|
||||
<field name="model">sf.workpiece.delivery.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<form js_class="remove_focus_form_view">
|
||||
<sheet>
|
||||
<field name="delivery_ids" invisible="True"/>
|
||||
<field name="workorder_id" invisible="True"/>
|
||||
<field name="type" invisible="True"/>
|
||||
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
|
||||
<field name="workorder_ids" invisible="True"/>
|
||||
<field name="delivery_type" invisible="True"/>
|
||||
<field name="confirm_button" invisible="1"/>
|
||||
<field name="_barcode_scanned" widget="barcode_handler"/>
|
||||
<group col="1">
|
||||
<field name="production_ids" readonly="1" widget="many2many_tags" string="制造订单号"/>
|
||||
<div class="o_address_format">
|
||||
<lable for="rfid_code"></lable>
|
||||
<field name="rfid_code" class="o_address_zip"/>
|
||||
<button name="recognize_production" string="识别" type="object" class="oe_highlight"/>
|
||||
</div>
|
||||
<field name="destination_production_line_id" readonly="1"/>
|
||||
<field name="route_id"/>
|
||||
<field name="delivery_type" readonly="1"/>
|
||||
<field name="feeder_station_start_id" string="当前接驳站" options="{'no_create': True}" required="1"/>
|
||||
<field name="workcenter_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
|
||||
<field name="feeder_station_start_id" focesave="1" readonly="1"/>
|
||||
<field name="feeder_station_destination_id" focesave="1" readonly="1"/>
|
||||
</group>
|
||||
<div attrs="{'invisible': [('type', 'in', ['上产线','下产线'])]}">
|
||||
是否确定配送
|
||||
</div>
|
||||
<footer>
|
||||
<button string="配送" name="confirm" type="object" class="oe_highlight"/>
|
||||
<button string="确认配送" name="confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
|
||||
<button string="确认解除" name="confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
|
||||
<button string="取消" class="btn btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</sheet>
|
||||
|
||||
@@ -1,88 +1,235 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from datetime import datetime
|
||||
from odoo import models, api, fields, _
|
||||
from datetime import datetime, date
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import models, api, fields
|
||||
|
||||
|
||||
def convert_datetime(obj):
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat() # 将 datetime 或 date 对象转换为 ISO 8601 字符串格式
|
||||
raise TypeError(f"Type {type(obj)} not serializable")
|
||||
|
||||
|
||||
class WorkpieceDeliveryWizard(models.TransientModel):
|
||||
_name = 'sf.workpiece.delivery.wizard'
|
||||
_inherit = ["barcodes.barcode_events_mixin"]
|
||||
_description = '工件配送'
|
||||
|
||||
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送')
|
||||
rfid_code = fields.Char('rfid码')
|
||||
workorder_id = fields.Many2one('mrp.workorder', string='工单')
|
||||
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送单')
|
||||
workorder_ids = fields.Many2many('mrp.workorder', string='工单')
|
||||
production_ids = fields.Many2many('mrp.production', string='制造订单号')
|
||||
destination_production_line_id = fields.Many2one('sf.production.line', '目的生产线')
|
||||
route_id = fields.Many2one('sf.agv.task.route', '任务路线', domain=[('route_type', 'in', ['上产线', '下产线'])])
|
||||
feeder_station_start_id = fields.Many2one('sf.agv.site', '起点接驳站')
|
||||
feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
|
||||
type = fields.Selection(
|
||||
[('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
|
||||
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True)
|
||||
confirm_button = fields.Char('按钮名称')
|
||||
|
||||
@api.onchange('delivery_type')
|
||||
def _onchange_type(self):
|
||||
if self.delivery_type:
|
||||
routes = self.env['sf.agv.task.route'].search([('route_type', '=', self.delivery_type)])
|
||||
if self.workcenter_id:
|
||||
routes = routes.filtered(lambda a: a.start_site_id.workcenter_id.id == self.workcenter_id.id)
|
||||
start_site_ids = routes.mapped('start_site_id.id')
|
||||
workcenter_ids = routes.mapped('end_site_id.workcenter_id.id')
|
||||
if workcenter_ids:
|
||||
self.workcenter_id = workcenter_ids[0]
|
||||
return {
|
||||
'domain':
|
||||
{
|
||||
'feeder_station_start_id': [('id', 'in', start_site_ids)],
|
||||
'workcenter_id': [('id', 'in', workcenter_ids)],
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'domain':
|
||||
{
|
||||
'feeder_station_start_id': [],
|
||||
'workcenter_id': [],
|
||||
}
|
||||
}
|
||||
|
||||
def _get_agv_route_type_selection(self):
|
||||
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
|
||||
|
||||
delivery_type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
|
||||
|
||||
def dispatch_confirm(self):
|
||||
if len(self.workorder_ids) < 4:
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'dispatch_confirm',
|
||||
'params': {
|
||||
'workorder_count': len(self.workorder_ids),
|
||||
'active_id': self.id,
|
||||
'context': self.env.context
|
||||
}
|
||||
}
|
||||
else:
|
||||
scheduling = self.confirm()
|
||||
# 显示通知
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'target': 'new',
|
||||
'params': {
|
||||
'message': f'任务下发成功!AGV任务调度编号为【{scheduling["name"]}】',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
||||
def confirm(self):
|
||||
if self.type != '运送空料架':
|
||||
if not self.route_id:
|
||||
raise UserError('请选择路线')
|
||||
if self.workorder_id:
|
||||
self.workorder_id.workpiece_delivery_ids[0]._delivery_avg()
|
||||
else:
|
||||
is_not_production_line = 0
|
||||
same_production_line_id = None
|
||||
notsame_production_line_arr = []
|
||||
for item in self.production_ids:
|
||||
if same_production_line_id is None:
|
||||
same_production_line_id = item.production_line_id.id
|
||||
if item.production_line_id.id != same_production_line_id:
|
||||
notsame_production_line_arr.append(item.name)
|
||||
notsame_production_line_str = ','.join(map(str, notsame_production_line_arr))
|
||||
if is_not_production_line >= 1:
|
||||
raise UserError('制造订单号为%s的目的生产线不一致' % notsame_production_line_str)
|
||||
else:
|
||||
self.delivery_ids._delivery_avg()
|
||||
try:
|
||||
if not self.workorder_ids:
|
||||
raise UserError('制造订单不能为空')
|
||||
|
||||
def recognize_production(self):
|
||||
# production_ids = []
|
||||
# delivery_ids = []
|
||||
# aa = self.production_ids.workorder_ids.filtered(
|
||||
# lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.filtered(
|
||||
# lambda c: c.rfid_code == self.rfid_code)
|
||||
# logging.info('aa:%s' % aa)
|
||||
if len(self.production_ids) == 4:
|
||||
raise UserError('只能配送四个制造订单')
|
||||
else:
|
||||
if self.rfid_code:
|
||||
wd = self.env['sf.workpiece.delivery'].search(
|
||||
[('type', '=', self.delivery_ids[0].type), ('rfid_code', '=', self.rfid_code),
|
||||
('status', '=', self.delivery_ids[0].status)])
|
||||
if wd:
|
||||
if wd.production_line_id.id == self.delivery_ids[0].production_line_id.id:
|
||||
# production_ids.append(wd.production_id)
|
||||
# delivery_ids.append(wd.id)
|
||||
# 将对象添加到对应的同模型且是多对多类型里
|
||||
self.production_ids |= wd.production_id
|
||||
self.delivery_ids |= wd
|
||||
self.rfid_code = False
|
||||
# self.production_ids = [(6, 0, production_ids)]
|
||||
# self.delivery_ids = [(6, 0, delivery_ids)]
|
||||
else:
|
||||
raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % wd.production_id.name)
|
||||
return {
|
||||
'name': _('确认'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'sf.workpiece.delivery.wizard',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_delivery_ids': [(6, 0, self.delivery_ids.ids)],
|
||||
'default_production_ids': [(6, 0, self.production_ids.ids)],
|
||||
'default_route_id': self.delivery_ids[0].route_id.id,
|
||||
'default_type': self.delivery_ids[0].type
|
||||
}}
|
||||
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
|
||||
agv_start_site_name=self.feeder_station_start_id.name,
|
||||
agv_route_type=self.delivery_type,
|
||||
workorders=self.workorder_ids,
|
||||
)
|
||||
# 如果关联了工件配送单,则修改状态为已下发
|
||||
if self.delivery_ids:
|
||||
val = {
|
||||
'status': '已下发',
|
||||
'agv_scheduling_id': scheduling.id,
|
||||
'feeder_station_start_id': scheduling.start_site_id.id,
|
||||
}
|
||||
# 如果agv任务已经下发,则修改工件配送单信息
|
||||
if scheduling.state == '配送中':
|
||||
val.update({
|
||||
'feeder_station_destination_id': scheduling.end_site_id.id,
|
||||
'route_id': scheduling.agv_route_id.id,
|
||||
'task_delivery_time': fields.Datetime.now()
|
||||
})
|
||||
self.delivery_ids.write(val)
|
||||
|
||||
# 如果是解除装夹工单,则需要处理工单逻辑
|
||||
for item in self.workorder_ids:
|
||||
if item.routing_type == '解除装夹' and item.state == 'ready':
|
||||
item.button_start()
|
||||
item.button_finish()
|
||||
|
||||
# return scheduling.read()[0]
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'target': 'new',
|
||||
'params': {
|
||||
'message': f'任务下发成功!AGV任务调度编号为【{scheduling.name}】',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logging.info('%s任务下发失败:%s', self.delivery_type, e)
|
||||
raise UserError(f'{self.delivery_type}任务下发失败:{e}') from e
|
||||
|
||||
# def recognize_production(self):
|
||||
# # production_ids = []
|
||||
# # delivery_ids = []
|
||||
# # aa = self.production_ids.workorder_ids.filtered(
|
||||
# # lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.filtered(
|
||||
# # lambda c: c.rfid_code == self.rfid_code)
|
||||
# # logging.info('aa:%s' % aa)
|
||||
# if len(self.production_ids) == 4:
|
||||
# raise UserError('只能配送四个制造订单')
|
||||
# else:
|
||||
# if self.rfid_code:
|
||||
# wd = self.env['sf.workpiece.delivery'].search(
|
||||
# [('type', '=', self.delivery_ids[0].type), ('rfid_code', '=', self.rfid_code),
|
||||
# ('status', '=', self.delivery_ids[0].status)])
|
||||
# if wd:
|
||||
# if wd.production_line_id.id == self.delivery_ids[0].production_line_id.id:
|
||||
# # production_ids.append(wd.production_id)
|
||||
# # delivery_ids.append(wd.id)
|
||||
# # 将对象添加到对应的同模型且是多对多类型里
|
||||
# self.production_ids |= wd.production_id
|
||||
# self.delivery_ids |= wd
|
||||
# self.rfid_code = False
|
||||
# # self.production_ids = [(6, 0, production_ids)]
|
||||
# # self.delivery_ids = [(6, 0, delivery_ids)]
|
||||
# else:
|
||||
# raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % wd.production_id.name)
|
||||
# return {
|
||||
# 'name': _('确认'),
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'view_mode': 'form',
|
||||
# 'res_model': 'sf.workpiece.delivery.wizard',
|
||||
# 'target': 'new',
|
||||
# 'context': {
|
||||
# 'default_delivery_ids': [(6, 0, self.delivery_ids.ids)],
|
||||
# 'default_production_ids': [(6, 0, self.production_ids.ids)],
|
||||
# 'default_route_id': self.delivery_ids[0].route_id.id,
|
||||
# 'default_type': self.delivery_ids[0].type
|
||||
# }}
|
||||
|
||||
@api.onchange('route_id')
|
||||
def onchange_route(self):
|
||||
if self.route_id:
|
||||
self.feeder_station_start_id = self.route_id.start_site_id.id
|
||||
self.feeder_station_destination_id = self.route_id.end_site_id.id
|
||||
|
||||
def on_barcode_scanned(self, barcode):
|
||||
delivery_type = self.env.context.get('default_delivery_type')
|
||||
# 判断barcode是否是数字
|
||||
if not barcode.isdigit():
|
||||
# 判断是否是AGV接驳站名称
|
||||
agv_site = self.env['sf.agv.site'].search([('name', '=', barcode)])
|
||||
self.feeder_station_start_id = agv_site.id # 修正:移除 .id
|
||||
return
|
||||
|
||||
if delivery_type == '上产线':
|
||||
workorder = self.env['mrp.workorder'].search(
|
||||
[('production_line_state', '=', '待上产线'), ('rfid_code', '=', barcode),
|
||||
('state', '=', 'done')])
|
||||
# 找到对应的配送单
|
||||
delivery = self.env['sf.workpiece.delivery'].search(
|
||||
[('type', '=', '上产线'), ('rfid_code', '=', barcode),
|
||||
('status', '=', '待下发')])
|
||||
if delivery:
|
||||
self.delivery_ids |= delivery
|
||||
elif delivery_type == '运送空料架':
|
||||
workorder = self.env['mrp.workorder'].search(
|
||||
[('routing_type', '=', '解除装夹'), ('rfid_code', '=', barcode),
|
||||
('state', '=', 'ready')])
|
||||
if workorder:
|
||||
if (len(self.production_ids) > 0 and
|
||||
workorder.production_line_id.id != self.production_ids[0].production_line_id.id):
|
||||
raise UserError(f'该rfid对应的制造订单号为{workorder.production_id.name}的目的生产线不一致')
|
||||
|
||||
# 调用打印成品条码方法
|
||||
workorder.print_method()
|
||||
|
||||
# 将对象添加到对应的同模型且是多对多类型里
|
||||
self.production_ids |= workorder.production_id
|
||||
self.workorder_ids |= workorder
|
||||
|
||||
down_product_agv_scheduling = workorder.get_down_product_agv_scheduling()
|
||||
if down_product_agv_scheduling:
|
||||
if not self.feeder_station_start_id:
|
||||
self.feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
|
||||
else:
|
||||
if self.feeder_station_start_id.id != down_product_agv_scheduling.end_site_id.id:
|
||||
raise UserError(f'该rfid不在{self.feeder_station_start_id.name}接驳站内')
|
||||
|
||||
else:
|
||||
raise UserError('该rfid码对应的工单不存在')
|
||||
return
|
||||
|
||||
@api.onchange('feeder_station_start_id')
|
||||
def on_start_id_change(self):
|
||||
if self.delivery_type == '运送空料架' and len(self.workorder_ids) > 0:
|
||||
down_product_agv_scheduling = self.workorder_ids[0].get_down_product_agv_scheduling()
|
||||
if down_product_agv_scheduling and self.feeder_station_start_id \
|
||||
and down_product_agv_scheduling.end_site_id.id != self.feeder_station_start_id.id:
|
||||
raise UserError('当前接驳站不匹配!')
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1
sf_message/__init__.py
Normal file
1
sf_message/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
25
sf_message/__manifest__.py
Normal file
25
sf_message/__manifest__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': '机企猫智能工厂 消息提醒',
|
||||
'version': '1.0',
|
||||
'summary': '智能工厂消息提醒模块',
|
||||
'sequence': 1,
|
||||
'description': """
|
||||
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.jikimo.com',
|
||||
'depends': ['base', 'sf_plan', 'sf_sale'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/sf_message_template_view.xml',
|
||||
],
|
||||
'test': [
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
}
|
||||
8
sf_message/models/__init__.py
Normal file
8
sf_message/models/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from . import sf_message_template
|
||||
from . import sf_message_sale
|
||||
from . import sf_message_plan
|
||||
from . import sf_message_stock_picking
|
||||
from . import sf_message_cam_program
|
||||
from . import sf_message_functional_tool_assembly
|
||||
from . import sf_message_purchase
|
||||
from . import sf_message_workorder
|
||||
6
sf_message/models/sf_message_cam_program.py
Normal file
6
sf_message/models/sf_message_cam_program.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessageCamProgram(models.Model):
|
||||
_name = 'sf.cam.work.order.program.knife.plan'
|
||||
_inherit = ['sf.cam.work.order.program.knife.plan', 'sf.message.template']
|
||||
6
sf_message/models/sf_message_functional_tool_assembly.py
Normal file
6
sf_message/models/sf_message_functional_tool_assembly.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessagefunctionalToolAssembly(models.Model):
|
||||
_name = 'sf.functional.tool.assembly'
|
||||
_inherit = ['sf.functional.tool.assembly', 'sf.message.template']
|
||||
6
sf_message/models/sf_message_plan.py
Normal file
6
sf_message/models/sf_message_plan.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessagePlan(models.Model):
|
||||
_name = 'sf.production.plan'
|
||||
_inherit = ['sf.production.plan', 'sf.message.template']
|
||||
6
sf_message/models/sf_message_purchase.py
Normal file
6
sf_message/models/sf_message_purchase.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessagePurchase(models.Model):
|
||||
_name = 'purchase.order'
|
||||
_inherit = ['purchase.order', 'sf.message.template']
|
||||
11
sf_message/models/sf_message_sale.py
Normal file
11
sf_message/models/sf_message_sale.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessageSale(models.Model):
|
||||
_name = 'sale.order'
|
||||
_inherit = ['sale.order', 'sf.message.template']
|
||||
|
||||
# def create(self):
|
||||
# res = super(SFMessageSale, self).create()
|
||||
# if res is True:
|
||||
|
||||
6
sf_message/models/sf_message_stock_picking.py
Normal file
6
sf_message/models/sf_message_stock_picking.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessageStockPicking(models.Model):
|
||||
_name = 'stock.picking'
|
||||
_inherit = ['stock.picking', 'sf.message.template']
|
||||
34
sf_message/models/sf_message_template.py
Normal file
34
sf_message/models/sf_message_template.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class SfMessageTemplate(models.Model):
|
||||
_name = "sf.message.template"
|
||||
_description = u'消息模板'
|
||||
|
||||
name = fields.Char(string=u"名称", required=True)
|
||||
description = fields.Char(string=u"描述")
|
||||
content = fields.Html(string=u"内容", render_engine='qweb', translate=True, prefetch=True, sanitize=False)
|
||||
msgtype = fields.Selection(
|
||||
[('text', u'文字'), ('markdown', u'Markdown')], u'消息类型',
|
||||
required=True, default='markdown')
|
||||
notification_department_id = fields.Many2one('hr.department', u'通知部门', required=True)
|
||||
notification_employee_ids = fields.Many2many('hr.employee', string=u'员工',
|
||||
domain="[('department_id', '=',notification_department_id)]",
|
||||
required=True)
|
||||
is_send_time = fields.Boolean(string=u"定时发送", default=False)
|
||||
send_time_1 = fields.Integer('发送时间点1')
|
||||
send_time_2 = fields.Integer('发送时间点2')
|
||||
active = fields.Boolean(string=u"是否有效", default=True)
|
||||
|
||||
@api.onchange('notification_department_id')
|
||||
def _clear_employee_ids(self):
|
||||
if self.notification_department_id:
|
||||
self.notification_employee_ids = False
|
||||
|
||||
@abstractmethod
|
||||
def dispatch(self, args):
|
||||
"""
|
||||
强迫继承该类必走该抽象方法'
|
||||
"""
|
||||
6
sf_message/models/sf_message_workorder.py
Normal file
6
sf_message/models/sf_message_workorder.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class SFMessageWork(models.Model):
|
||||
_name = 'mrp.workorder'
|
||||
_inherit = ['mrp.workorder', 'sf.message.template']
|
||||
5
sf_message/security/group_security.xml
Normal file
5
sf_message/security/group_security.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
15
sf_message/security/ir.model.access.csv
Normal file
15
sf_message/security/ir.model.access.csv
Normal file
@@ -0,0 +1,15 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_sf_message_template_group_sale_salemanager,sf_message_template,model_sf_message_template,sf_base.group_sale_salemanager,1,1,1,0
|
||||
access_sf_message_template_group_purchase,sf_message_template,model_sf_message_template,sf_base.group_purchase,1,1,1,0
|
||||
access_sf_message_template_group_sf_stock_user,sf_message_template,model_sf_message_template,sf_base.group_sf_stock_user,1,1,1,0
|
||||
access_sf_message_template_group_sf_order_user,sf_message_template,model_sf_message_template,sf_base.group_sf_order_user,1,1,1,0
|
||||
access_sf_message_template_group_sf_tool_user,sf_message_template,model_sf_message_template,sf_base.group_sf_tool_user,1,1,1,0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
81
sf_message/views/sf_message_template_view.xml
Normal file
81
sf_message/views/sf_message_template_view.xml
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- © <2016> <top hy>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
|
||||
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="sf_message_template_view_form" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.view.form</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="消息模板">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1>
|
||||
<field name="name" class="w-100" required="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<!-- <field name="type"/>-->
|
||||
<field name="content" widget="html" class="oe-bordered-editor"
|
||||
options="{'style-inline': true, 'codeview': true, 'dynamic_placeholder': true}"/>
|
||||
<field name="description"/>
|
||||
<field name="msgtype"/>
|
||||
<field name="is_send_time"/>
|
||||
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="notification_department_id"/>
|
||||
<field name="notification_employee_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_message_template_view_tree" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.view.tree</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="消息模板">
|
||||
<field name="name"/>
|
||||
<!-- <field name="type"/>-->
|
||||
<field name="content"/>
|
||||
<field name="msgtype"/>
|
||||
<field name="is_send_time"/>
|
||||
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
|
||||
<field name="notification_department_id"/>
|
||||
<field name="notification_employee_ids" widget="many2many_tags"/>
|
||||
<field name="description"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sf_message_template_search_view" model="ir.ui.view">
|
||||
<field name="name">sf.message.template.search.view</field>
|
||||
<field name="model">sf.message.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name" string="模糊搜索"
|
||||
filter_domain="['|','|',('name','like',self),('description','like',self)]"/>
|
||||
<field name="name"/>
|
||||
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--定义单证类型视图动作-->
|
||||
<record id="sf_message_template_action" model="ir.actions.act_window">
|
||||
<field name="name">消息模板</field>
|
||||
<field name="res_model">sf.message.template</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="sf_message_template_view_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="msg_set_menu" name="消息设置" parent="base.menu_administration" sequence="1"/>
|
||||
<menuitem id="sf_message_template_send_menu" name="消息模板" parent="msg_set_menu"
|
||||
action="sf_message_template_action" sequence="1"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -10,11 +10,12 @@
|
||||
""",
|
||||
'category': 'sf',
|
||||
'website': 'https://www.sf.cs.jikimo.com',
|
||||
'depends': ['sf_base', 'base_setup'],
|
||||
'depends': ['sf_base', 'base_setup','sf_bf_connect'],
|
||||
'data': [
|
||||
'data/ir_cron_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/res_config_settings_views.xml'
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/order_price.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
@@ -24,11 +24,13 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
ret = json.loads(datas)
|
||||
ret = json.loads(ret['result'])
|
||||
logging.info('下发编程单:%s' % ret)
|
||||
domain = [('programming_no', '=', ret['programming_no'])]
|
||||
if ret['manufacturing_type'] == 'scrap':
|
||||
domain += [('state', 'not in', ['done', 'scrap', 'cancel'])]
|
||||
productions = request.env['mrp.production'].with_user(
|
||||
request.env.ref("base.user_admin")).search(
|
||||
[('programming_no', '=', ret['programming_no'])])
|
||||
request.env.ref("base.user_admin")).search(domain)
|
||||
if productions:
|
||||
# # 拉取所有加工面的程序文件
|
||||
# 拉取所有加工面的程序文件
|
||||
for r in ret['processing_panel'].split(','):
|
||||
program_path_tmp_r = os.path.join('/tmp', ret['folder_name'], 'return', r)
|
||||
if os.path.exists(program_path_tmp_r):
|
||||
@@ -48,45 +50,28 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
if not production.workorder_ids:
|
||||
production.product_id.model_processing_panel = ret['processing_panel']
|
||||
production._create_workorder(ret)
|
||||
# else:
|
||||
# for panel in ret['processing_panel'].split(','):
|
||||
# # 查询状态为进行中且工序类型为CNC加工的工单
|
||||
# cnc_workorder = production.workorder_ids.filtered(
|
||||
# lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done',
|
||||
# 'cancel'] and ac.processing_panel == panel)
|
||||
# if cnc_workorder:
|
||||
# if cnc_workorder.cnc_ids:
|
||||
# cnc_workorder.cmm_ids.sudo().unlink()
|
||||
# cnc_workorder.cnc_ids.sudo().unlink()
|
||||
# request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
# production)
|
||||
# # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
|
||||
# # panel)
|
||||
# program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
|
||||
# logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel)
|
||||
# files_panel = os.listdir(program_path_tmp_panel)
|
||||
# if files_panel:
|
||||
# for file in files_panel:
|
||||
# file_extension = os.path.splitext(file)[1]
|
||||
# logging.info('file_extension:%s' % file_extension)
|
||||
# if file_extension.lower() == '.pdf':
|
||||
# panel_file_path = os.path.join(program_path_tmp_panel, file)
|
||||
# logging.info('panel_file_path:%s' % panel_file_path)
|
||||
# cnc_workorder.write(
|
||||
# {'cnc_ids': cnc_workorder.cnc_ids.sudo()._json_cnc_processing(panel, ret),
|
||||
# 'cmm_ids': cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel, ret),
|
||||
# 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
# pre_workorder = production.workorder_ids.filtered(
|
||||
# lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done',
|
||||
# 'cancel'] and ap.processing_panel == panel)
|
||||
# if pre_workorder:
|
||||
# pre_workorder.write(
|
||||
# {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
productions.process_range_time()
|
||||
else:
|
||||
for panel in ret['processing_panel'].split(','):
|
||||
# 查询状态为进行中且工序类型为CNC加工的工单
|
||||
cnc_workorder_has = production.workorder_ids.filtered(
|
||||
lambda ach: ach.routing_type == 'CNC加工' and ach.state not in ['progress', 'done',
|
||||
'rework',
|
||||
'cancel'] and ach.processing_panel == panel)
|
||||
if cnc_workorder_has:
|
||||
if cnc_workorder_has.cnc_ids:
|
||||
cnc_workorder_has.cmm_ids.sudo().unlink()
|
||||
cnc_workorder_has.cnc_ids.sudo().unlink()
|
||||
request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
|
||||
production)
|
||||
cnc_workorder_has.write(
|
||||
{'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret),
|
||||
'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)})
|
||||
for panel in ret['processing_panel'].split(','):
|
||||
# 查询状态为进行中且工序类型为CNC加工的工单
|
||||
cnc_workorder = productions.workorder_ids.filtered(
|
||||
lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done',
|
||||
'cancel'] and ac.processing_panel == panel)
|
||||
lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', 'rework'
|
||||
'cancel'] and ac.processing_panel == panel)
|
||||
if cnc_workorder:
|
||||
# program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
|
||||
# panel)
|
||||
@@ -102,18 +87,12 @@ class Sf_Mrs_Connect(http.Controller):
|
||||
logging.info('panel_file_path:%s' % panel_file_path)
|
||||
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
pre_workorder = productions.workorder_ids.filtered(
|
||||
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done',
|
||||
'cancel'] and ap.processing_panel == panel)
|
||||
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', 'rework'
|
||||
'cancel'] and ap.processing_panel == panel)
|
||||
if pre_workorder:
|
||||
pre_workorder.write(
|
||||
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
|
||||
cnc_program_ids = [item.id for item in productions]
|
||||
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
|
||||
[('production_id', 'in', cnc_program_ids)])
|
||||
if workpiece_delivery:
|
||||
workpiece_delivery.write(
|
||||
{'is_cnc_program_down': True, 'production_line_id': productions.production_line_id.id})
|
||||
return json.JSONEncoder().encode(res)
|
||||
else:
|
||||
res = {'status': 0, 'message': '该制造订单暂未开始'}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from . import ftp_operate
|
||||
from . import res_config_setting
|
||||
from . import sync_common
|
||||
from . import order_price
|
||||
29
sf_mrs_connect/models/order_price.py
Normal file
29
sf_mrs_connect/models/order_price.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class OrderPrice(models.Model):
|
||||
_name = 'order.price'
|
||||
_description = '订单价格对比'
|
||||
sale_order_id = fields.Many2one('sale.order', '销售订单')
|
||||
bfm_order_name = fields.Char(related="sale_order_id.default_code", string='bfm订单号')
|
||||
sale_order_name = fields.Char(related="sale_order_id.name", string='销售订单号')
|
||||
currency_id = fields.Many2one(
|
||||
related='sale_order_id.currency_id', string='货币', store=True)
|
||||
sale_order_amount_total = fields.Monetary(related="sale_order_id.amount_total", tracking=4, string='销售订单金额')
|
||||
bfm_amount_total = fields.Float(string='价格合计', compute='_compute_bfm_amount_total', store=True)
|
||||
|
||||
def is_float(self,value):
|
||||
try:
|
||||
float(value)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
@api.depends('sale_order_id.remark')
|
||||
def _compute_bfm_amount_total(self):
|
||||
for record in self:
|
||||
amount_total = 0
|
||||
for line in record.sale_order_id.order_line:
|
||||
# 判断remark是否存在并且是否是数字
|
||||
if line.remark and self.is_float(line.remark):
|
||||
amount_total += float(line.remark)
|
||||
record.bfm_amount_total = amount_total
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user