5D3420315FA54219AB2427B0C674AD66auto
This commit is contained in:
parent
98d889f15c
commit
0a8d4e8c75
20
Main.seq
20
Main.seq
|
|
@ -142,7 +142,7 @@
|
||||||
},
|
},
|
||||||
"NodeProperty": {
|
"NodeProperty": {
|
||||||
"EP_CONTROL": {
|
"EP_CONTROL": {
|
||||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCAoCiAgICBmaWxsX2NvbnRyYWN0X2dvb2RzX2xpbmVzLCBmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbywKICAgIHVwbG9hZF9jb250cmFjdF9kb2N1bWVudF9pZl9uZWVkZWQsIHN1Ym1pdF9jb250cmFjdF9mb3JfYXBwcm92YWwsCiAgICByZWFkX2NvbnRyYWN0X251bWJlcgopCgphZGFwdGVyID0gc2VsZi5fYWRhcHRlcgoKcHJpbnQoIltTMDldIOWhq+WGmei0p+WTgeaYjue7hi4uLiIpCmZpbGxfY29udHJhY3RfZ29vZHNfbGluZXMoYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g6LSn5ZOBOiB7bGVuKHNlbGYuX2N0eC5nb29kc19saW5lcyl9IOihjCIpCnByaW50KCJbUzEwXSDloavlhpnku5jmrL7kv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDku5jmrL7mlrnlvI89e3NlbGYuX2N0eC5wYXltZW50X21ldGhvZH0iKQoKaWYgc2VsZi5fY3R4LnNvdXJjZV90eXBlID09ICJZWUMiOgogICAgcHJpbnQoIltTMTFdIOWQiOW5tlBERiBbWVlD5LiT5bGeXS4uLiAo5Y2VUERGLCDot7Pov4flkIjlubYpIikKICAgIHByaW50KCJbUzEyXSDkuIrkvKBQREbpmYTku7YgW1lZQ+S4k+Wxnl0uLi4gKGRyeV9ydW4g6Lez6L+HKSIpCgpwcmludCgpCnByaW50KCJbUzEzXSDmj5DkuqTlrqHmibkuLi4iKQppZiBub3Qgc3VibWl0X2NvbnRyYWN0X2Zvcl9hcHByb3ZhbChhZGFwdGVyLCBzZWxmLl9jdHgpOgogICAgcHJpbnQoIltEUlktUlVOXSDmvJTnu4PmqKHlvI/vvIzot7Pov4flrp7pmYXmj5DkuqQiKQogICAgcHJpbnQoIj0iICogNjApCiAgICBwcmludCgi5ZCI5ZCM5bu65Y2V5rWB56iL5ryU57uD5a6M5oiQIikKICAgIHByaW50KCI9IiAqIDYwKQplbHNlOgogICAgcHJpbnQoIltTMTRdIOivu+WPluWQiOWQjOe8luWPty4uLiIpCiAgICBjb250cmFjdF9ubyA9IHJlYWRfY29udHJhY3RfbnVtYmVyKGFkYXB0ZXIpCiAgICBwcmludChmIltPS10g5ZCI5ZCM57yW5Y+3OiB7Y29udHJhY3Rfbm99IikKICAgIHByaW50KCI9IiAqIDYwKQogICAgcHJpbnQoZiJbQ09NUExFVEVEXSDlkIjlkIzliJvlu7rmiJDlip86IHtjb250cmFjdF9ub30iKQogICAgcHJpbnQoIj0iICogNjApCg==\"\n}"
|
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCAoCiAgICBmaWxsX2NvbnRyYWN0X2dvb2RzX2xpbmVzLCBmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbywKICAgIHVwbG9hZF9jb250cmFjdF9kb2N1bWVudF9pZl9uZWVkZWQsIHN1Ym1pdF9jb250cmFjdF9mb3JfYXBwcm92YWwsCiAgICByZWFkX2NvbnRyYWN0X251bWJlcgopCgphZGFwdGVyID0gc2VsZi5fYWRhcHRlcgoKcHJpbnQoIltTMDldIOWhq+WGmei0p+WTgeaYjue7hi4uLiIpCmZpbGxfY29udHJhY3RfZ29vZHNfbGluZXMoYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g6LSn5ZOBOiB7bGVuKHNlbGYuX2N0eC5nb29kc19saW5lcyl9IOihjCIpCnByaW50KCJbUzEwXSDloavlhpnku5jmrL7kv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3BheW1lbnRfaW5mbyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDku5jmrL7mlrnlvI89e3NlbGYuX2N0eC5wYXltZW50X21ldGhvZH0iKQoKaWYgc2VsZi5fY3R4LnNvdXJjZV90eXBlID09ICJZWUMiOgogICAgcHJpbnQoIltTMTFdIOWQiOW5tlBERiBbWVlD5LiT5bGeXS4uLiAo5Y2VUERGLCDot7Pov4flkIjlubYpIikKICAgIHByaW50KCJbUzEyXSDkuIrkvKBQREbpmYTku7YgW1lZQ+S4k+Wxnl0uLi4gKGRyeV9ydW4g6Lez6L+HKSIpCgpwcmludCgpCnByaW50KCJbUzEzXSDmj5DkuqTlrqHmibkuLi4iKQppZiBub3Qgc3VibWl0X2NvbnRyYWN0X2Zvcl9hcHByb3ZhbChhZGFwdGVyLCBzZWxmLl9jdHgpOgogICAgcHJpbnQoIltEUlktUlVOXSDmvJTnu4PmqKHlvI/vvIzot7Pov4flrp7pmYXmj5DkuqQiKQogICAgcHJpbnQoIj0iICogNjApCiAgICBwcmludCgi5ZCI5ZCM5bu65Y2V5rWB56iL5ryU57uD5a6M5oiQIikKICAgIHByaW50KCI9IiAqIDYwKQplbHNlOgogICAgcHJpbnQoIltTMTRdIOivu+WPluWQiOWQjOe8luWPty4uLiIpCiAgICBjb250cmFjdF9ubyA9IHJlYWRfY29udHJhY3RfbnVtYmVyKGFkYXB0ZXIpCiAgICBwcmludChmIltPS10g5ZCI5ZCM57yW5Y+3OiB7Y29udHJhY3Rfbm99IikKICAgIHByaW50KCI9IiAqIDYwKQogICAgcHJpbnQoZiJbQ09NUExFVEVEXSDlkIjlkIzliJvlu7rmiJDlip86IHtjb250cmFjdF9ub30iKQogICAgcHJpbnQoIj0iICogNjApCg==\"\n}"
|
||||||
},
|
},
|
||||||
"EP_Gen": {
|
"EP_Gen": {
|
||||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
|
|
@ -175,7 +175,7 @@
|
||||||
},
|
},
|
||||||
"NodeProperty": {
|
"NodeProperty": {
|
||||||
"EP_CONTROL": {
|
"EP_CONTROL": {
|
||||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8sIGZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcwoKYWRhcHRlciA9IHNlbGYuX2FkYXB0ZXIKCnByaW50KCJbUzA3XSDloavlhpnln7rnoYDooajljZUuLi4iKQpmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8oYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g57G75YirPXtzZWxmLl9jdHguY29udHJhY3RfY2F0ZWdvcnl9LCDnu5Pnrpc9e3NlbGYuX2N0eC5zZXR0bGVtZW50X3R5cGV9IikKcHJpbnQoIltTMDhdIOWhq+WGmemHkemineS4juaXpeacny4uLiIpCmZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDph5Hpop09e3NlbGYuX2N0eC5hbW91bnR9LCDnu5PmnZ/ml6XmnJ89e3NlbGYuX2N0eC5jb250cmFjdF9lbmRfZGF0ZX0iKQo=\"\n}"
|
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8sIGZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcwoKYWRhcHRlciA9IHNlbGYuX2FkYXB0ZXIKCnByaW50KCJbUzA3XSDloavlhpnln7rnoYDooajljZUuLi4iKQpmaWxsX2NvbnRyYWN0X2Jhc2ljX2luZm8oYWRhcHRlciwgc2VsZi5fY3R4KQpwcmludChmIltPS10g57G75YirPXtzZWxmLl9jdHguY29udHJhY3RfY2F0ZWdvcnl9LCDnu5Pnrpc9e3NlbGYuX2N0eC5zZXR0bGVtZW50X3R5cGV9IikKcHJpbnQoIltTMDhdIOWhq+WGmemHkemineS4juaXpeacny4uLiIpCmZpbGxfY29udHJhY3RfYW1vdW50X2FuZF9kYXRlcyhhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KGYiW09LXSDph5Hpop09e3NlbGYuX2N0eC5hbW91bnR9LCDnu5PmnZ/ml6XmnJ89e3NlbGYuX2N0eC5jb250cmFjdF9lbmRfZGF0ZX0iKQo=\"\n}"
|
||||||
},
|
},
|
||||||
"EP_Gen": {
|
"EP_Gen": {
|
||||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
|
|
@ -208,7 +208,7 @@
|
||||||
},
|
},
|
||||||
"NodeProperty": {
|
"NodeProperty": {
|
||||||
"EP_CONTROL": {
|
"EP_CONTROL": {
|
||||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBzZWxlY3RfY29udHJhY3RfdGVtcGxhdGUsIGZpbGxfY29udHJhY3Rfc2lnbmF0b3J5X2luZm8KCmFkYXB0ZXIgPSBzZWxmLl9hZGFwdGVyCgpwcmludCgiW1MwNV0g6YCJ5oup5ZCI5ZCM5qih5p2/Li4uIikKc2VsZWN0X2NvbnRyYWN0X3RlbXBsYXRlKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOaooeadvzoge3NlbGYuX2N0eC50ZW1wbGF0ZV9uYW1lfSIpCnByaW50KCJbUzA2XSDloavlhpnnrb7nuqbmlrnkv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3NpZ25hdG9yeV9pbmZvKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOetvue6puaWuToge3NlbGYuX2N0eC5jdXN0b21lcl9uYW1lfSIpCg==\"\n}"
|
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBzZWxlY3RfY29udHJhY3RfdGVtcGxhdGUsIGZpbGxfY29udHJhY3Rfc2lnbmF0b3J5X2luZm8KCmFkYXB0ZXIgPSBzZWxmLl9hZGFwdGVyCgpwcmludCgiW1MwNV0g6YCJ5oup5ZCI5ZCM5qih5p2/Li4uIikKc2VsZWN0X2NvbnRyYWN0X3RlbXBsYXRlKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOaooeadvzoge3NlbGYuX2N0eC50ZW1wbGF0ZV9uYW1lfSIpCnByaW50KCJbUzA2XSDloavlhpnnrb7nuqbmlrnkv6Hmga8uLi4iKQpmaWxsX2NvbnRyYWN0X3NpZ25hdG9yeV9pbmZvKGFkYXB0ZXIsIHNlbGYuX2N0eCkKcHJpbnQoZiJbT0tdIOetvue6puaWuToge3NlbGYuX2N0eC5jdXN0b21lcl9uYW1lfSIpCg==\"\n}"
|
||||||
},
|
},
|
||||||
"EP_Gen": {
|
"EP_Gen": {
|
||||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
|
|
@ -241,7 +241,7 @@
|
||||||
},
|
},
|
||||||
"NodeProperty": {
|
"NodeProperty": {
|
||||||
"EP_CONTROL": {
|
"EP_CONTROL": {
|
||||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBsb2dpbl9jb250cmFjdF9zeXN0ZW0sIGVudGVyX2NvbnRyYWN0X2NyZWF0ZQpmcm9tIHJwYV95aXNhaXFpLmFwYV9icm93c2VyX2FkYXB0ZXIgaW1wb3J0IEFwYUJyb3dzZXJBZGFwdGVyCgphZGFwdGVyID0gQXBhQnJvd3NlckFkYXB0ZXIoKQphZGFwdGVyLl9icm93c2VyID0gc2VsZi5fYnJvd3NlcgphZGFwdGVyLl9pc19vcGVuID0gVHJ1ZQoKcHJpbnQoIltTMDNdIOeZu+W9leWQiOWQjOezu+e7ny4uLiIpCmxvZ2luX2NvbnRyYWN0X3N5c3RlbShhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KCJbT0tdIOeZu+W9leWujOaIkCIpCnByaW50KCJbUzA0XSDov5vlhaXlkIjlkIzotbfojYkuLi4iKQplbnRlcl9jb250cmFjdF9jcmVhdGUoYWRhcHRlcikKcHJpbnQoIltPS10g5bey6L+b5YWl5ZCI5ZCM6LW36I2J6aG16Z2iIikKc2VsZi5fYWRhcHRlciA9IGFkYXB0ZXIK\"\n}"
|
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5jb250cmFjdF9mbG93X3N0ZXBzIGltcG9ydCBsb2dpbl9jb250cmFjdF9zeXN0ZW0sIGVudGVyX2NvbnRyYWN0X2NyZWF0ZQoKYWRhcHRlciA9IHNlbGYuX2FkYXB0ZXIKcHJpbnQoIltTMDNdIOeZu+W9leWQiOWQjOezu+e7ny4uLiIpCmxvZ2luX2NvbnRyYWN0X3N5c3RlbShhZGFwdGVyLCBzZWxmLl9jdHgpCnByaW50KCJbT0tdIOeZu+W9leWujOaIkCIpCnByaW50KCJbUzA0XSDov5vlhaXlkIjlkIzotbfojYkuLi4iKQplbnRlcl9jb250cmFjdF9jcmVhdGUoYWRhcHRlcikKcHJpbnQoIltPS10g5bey6L+b5YWl5ZCI5ZCM6LW36I2J6aG16Z2iIikK\"\n}"
|
||||||
},
|
},
|
||||||
"EP_Gen": {
|
"EP_Gen": {
|
||||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
|
|
@ -274,12 +274,12 @@
|
||||||
},
|
},
|
||||||
"NodeProperty": {
|
"NodeProperty": {
|
||||||
"EP_CONTROL": {
|
"EP_CONTROL": {
|
||||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"ZnJvbSB1YnBhLmlvYmplY3QuYnJvd3NlciBpbXBvcnQgQnJvd3Nlcgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2Zsb3dfc3RlcHMgaW1wb3J0IG9wZW5fY29udHJhY3Rfc3lzdGVtCgpwcmludCgiW1MwMl0g5omT5byAIEVkZ2Ug5rWP6KeI5ZmoLi4uIikKcHJpbnQoZiIgIFVSTDoge3NlbGYuX2N0eC5jb250cmFjdF91cmx9IikKYnJvd3NlciA9IEJyb3dzZXIoQnJvd3Nlci5BcHBOYW1lLmVkZ2UpCmJyb3dzZXIub3Blbih1cmw9c2VsZi5fY3R4LmNvbnRyYWN0X3VybCwgdGltZW91dD0zMCkKc2VsZi5fYnJvd3NlciA9IGJyb3dzZXIKcHJpbnQoIltPS10g5rWP6KeI5Zmo5bey5omT5byAIikK\"\n}"
|
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"ZnJvbSBycGFfeWlzYWlxaS5hcGFfYnJvd3Nlcl9hZGFwdGVyIGltcG9ydCBBcGFCcm93c2VyQWRhcHRlcgoKcHJpbnQoIltTMDJdIOWQr+WKqCBDaHJvbWUgKyDmiZPlvIDlkIjlkIzns7vnu58uLi4iKQpwcmludChmIiAgVVJMOiB7c2VsZi5fY3R4LmNvbnRyYWN0X3VybH0iKQphZGFwdGVyID0gQXBhQnJvd3NlckFkYXB0ZXIodXJsPXNlbGYuX2N0eC5jb250cmFjdF91cmwpCmFkYXB0ZXIub3Blbl91cmwoc2VsZi5fY3R4LmNvbnRyYWN0X3VybCkKc2VsZi5fYWRhcHRlciA9IGFkYXB0ZXIKcHJpbnQoIltPS10gQ2hyb21lIOW3suaJk+W8gOW5tuWvvOiIquWIsOWQiOWQjOezu+e7nyIpCg==\"\n}"
|
||||||
},
|
},
|
||||||
"EP_Gen": {
|
"EP_Gen": {
|
||||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
"EP_Gen_BEFOREDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_BEFOREDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
"EP_Gen_NOTE": "{\n \"val\": \"5omT5byARWRnZea1j+iniOWZqOW5tuWvvOiIquWIsOWQiOWQjOezu+e7nw==\"\n}"
|
"EP_Gen_NOTE": "{\n \"val\": \"U2VsZW5pdW3miZPlvIBDaHJvbWXmtY/op4jlmajlubblr7zoiKrliLDlkIjlkIzns7vnu58=\"\n}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NodeUI": {
|
"NodeUI": {
|
||||||
|
|
@ -307,7 +307,7 @@
|
||||||
},
|
},
|
||||||
"NodeProperty": {
|
"NodeProperty": {
|
||||||
"EP_CONTROL": {
|
"EP_CONTROL": {
|
||||||
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"c3lzLCBvcywganNvbiwgcGF0aGxpYiwgdGltZQ==\",\n \"PBCode\": \"aW1wb3J0IHN5cywgb3MsIGpzb24sIHRpbWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgpfQ09ERVNfRElSID0gUGF0aChyJ2Q6XFJQQVxBdXRvUnBhXGNvZGVzJykKaWYgc3RyKF9DT0RFU19ESVIpIG5vdCBpbiBzeXMucGF0aDoKICAgIHN5cy5wYXRoLmluc2VydCgwLCBzdHIoX0NPREVTX0RJUikpCgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2Zsb3dfc3RlcHMgaW1wb3J0IENvbnRyYWN0Rmxvd0NvbnRleHQsIENvbnRyYWN0R29vZHNMaW5lCgpwcmludCgiPSIgKiA2MCkKcHJpbnQoIuWQiOWQjOW7uuWNlSBDT05UUkFDVF9DUkVBVEUgLSDliJ3lp4vljJYiKQpwcmludCgiPSIgKiA2MCkKCnNlbGYuX2N0eCA9IENvbnRyYWN0Rmxvd0NvbnRleHQoCiAgICBjb250cmFjdF91cmw9Imh0dHA6Ly9jb250cmFjdC10ZXN0Lmd6bXBjLmNvbTo4MDgwLyIsCiAgICB1c2VybmFtZT0idGVzdDAwMiIsIHBhc3N3b3JkPSIxIiwKICAgIHN1cHBsaWVyX25hbWU9Iua1i+ivleS+m+W6lOWVhiIsCiAgICBvcmRlcl9kYXRlPSIyMDI2LTA4LTEyIiwKICAgIHNvdXJjZV90eXBlPSJZWUMiLCB0ZW1wbGF0ZV90eXBlPSJOT05fRk9STUFUIiwKICAgIHRlbXBsYXRlX25hbWU9IumdnuagvOW8jy0t6YeH6LSt5Y2P6K6uIiwKICAgIGN1c3RvbWVyX2NvZGU9IkNVU1QtMDAxIiwgY3VzdG9tZXJfbmFtZT0i5rWL6K+V5L6b5bqU5ZWGIiwKICAgIGNvbnRyYWN0X2NhdGVnb3J5PSLph4fotK3ljY/orq4iLCBzZXR0bGVtZW50X3R5cGU9IuS7mOasviIsCiAgICBjb250cmFjdF9iYWNrZ3JvdW5kPSLoh6rliqjljJbmtYvor5XlkIjlkIzotbfojYkiLCBjb250cmFjdF9uYXR1cmU9IuS4u+WQiOWQjCIsCiAgICBhbW91bnRfY3VycmVuY3k9IuS6uuawkeW4gSIsIGFtb3VudD0iNTAwMDAuMDAiLAogICAgY29udHJhY3RfZW5kX2RhdGU9IjIwMjYtMTItMzEiLAogICAgaXNfbWFqb3JfY29udHJhY3Q9IuWQpiIsIGlzX3ByZXBheW1lbnQ9IuWQpiIsCiAgICBiZWxvbmdfeWVhcj0iMjAyNiIsIHBheW1lbnRfbWV0aG9kPSLotKbmnJ/mlK/ku5giLAogICAgcGF5bWVudF9kYXlzPSIzMCIsIGNvbnRyYWN0X3N0YXR1cz0i5q2j5byP5ZCI5ZCMIiwKICAgIGNvbnRhaW5zX2Jsb29kX3Byb2R1Y3RzPSLlkKYiLAogICAgc3VibWl0X21vZGU9ImRyeV9ydW4iLAogICAgZ29vZHNfbGluZXM9WwogICAgICAgIENvbnRyYWN0R29vZHNMaW5lKAogICAgICAgICAgICBvcGVyYXRpb25fY29kZT0iQUREIiwgZ29vZHNfY29kZT0iWVAtMDAxIiwKICAgICAgICAgICAgZ29vZHNfbmFtZT0i5rWL6K+V6I2v5ZOBQSIsIGFwcHJvdmFsX25vPSLlm73oja/lh4blrZdIMDAwMDAwMDEiLAogICAgICAgICAgICBzcGVjPSI1MG1nKjMw54mHIiwgdW5pdD0i55uSIiwgcXVhbnRpdHk9MTAuMCwKICAgICAgICApLAogICAgXSwKICAgIG1lcmdlZF9wZGZfcGF0aD1Ob25lLAopCgpwcmludChmIltPS10g5LiK5LiL5paH5bCx57uqOiBzb3VyY2U9e3NlbGYuX2N0eC5zb3VyY2VfdHlwZX0iKQpwcmludChmIltPS10g5qih5p2/OiB7c2VsZi5fY3R4LnRlbXBsYXRlX25hbWV9IikKcHJpbnQoZiJbT0tdIOi0p+WTgeihjOaVsDoge2xlbihzZWxmLl9jdHguZ29vZHNfbGluZXMpfSIpCg==\"\n}"
|
"EP_CONTROL_IMPORT": "{\n \"PImport\": \"\",\n \"PBCode\": \"aW1wb3J0IHN5cywgb3MsIGpzb24sIHRpbWUKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgpfQ09ERVNfRElSID0gUGF0aChyJ2Q6XFJQQVxBdXRvUnBhXGNvZGVzJykKaWYgc3RyKF9DT0RFU19ESVIpIG5vdCBpbiBzeXMucGF0aDoKICAgIHN5cy5wYXRoLmluc2VydCgwLCBzdHIoX0NPREVTX0RJUikpCgpmcm9tIHJwYV95aXNhaXFpLmNvbnRyYWN0X2Zsb3dfc3RlcHMgaW1wb3J0IENvbnRyYWN0Rmxvd0NvbnRleHQsIENvbnRyYWN0R29vZHNMaW5lCgpwcmludCgiPSIgKiA2MCkKcHJpbnQoIuWQiOWQjOW7uuWNlSBDT05UUkFDVF9DUkVBVEUgLSDliJ3lp4vljJYiKQpwcmludCgiPSIgKiA2MCkKCnNlbGYuX2N0eCA9IENvbnRyYWN0Rmxvd0NvbnRleHQoCiAgICBjb250cmFjdF91cmw9Imh0dHA6Ly9jb250cmFjdC10ZXN0Lmd6bXBjLmNvbTo4MDgwLyIsCiAgICB1c2VybmFtZT0idGVzdDAwMiIsIHBhc3N3b3JkPSIxIiwKICAgIHN1cHBsaWVyX25hbWU9Iua1i+ivleS+m+W6lOWVhiIsCiAgICBvcmRlcl9kYXRlPSIyMDI2LTA4LTEyIiwKICAgIHNvdXJjZV90eXBlPSJZWUMiLCB0ZW1wbGF0ZV90eXBlPSJOT05fRk9STUFUIiwKICAgIHRlbXBsYXRlX25hbWU9IumdnuagvOW8jy0t6YeH6LSt5Y2P6K6uIiwKICAgIGN1c3RvbWVyX2NvZGU9IkNVU1QtMDAxIiwgY3VzdG9tZXJfbmFtZT0i5rWL6K+V5L6b5bqU5ZWGIiwKICAgIGNvbnRyYWN0X2NhdGVnb3J5PSLph4fotK3ljY/orq4iLCBzZXR0bGVtZW50X3R5cGU9IuS7mOasviIsCiAgICBjb250cmFjdF9iYWNrZ3JvdW5kPSLoh6rliqjljJbmtYvor5XlkIjlkIzotbfojYkiLCBjb250cmFjdF9uYXR1cmU9IuS4u+WQiOWQjCIsCiAgICBhbW91bnRfY3VycmVuY3k9IuS6uuawkeW4gSIsIGFtb3VudD0iNTAwMDAuMDAiLAogICAgY29udHJhY3RfZW5kX2RhdGU9IjIwMjYtMTItMzEiLAogICAgaXNfbWFqb3JfY29udHJhY3Q9IuWQpiIsIGlzX3ByZXBheW1lbnQ9IuWQpiIsCiAgICBiZWxvbmdfeWVhcj0iMjAyNiIsIHBheW1lbnRfbWV0aG9kPSLotKbmnJ/mlK/ku5giLAogICAgcGF5bWVudF9kYXlzPSIzMCIsIGNvbnRyYWN0X3N0YXR1cz0i5q2j5byP5ZCI5ZCMIiwKICAgIGNvbnRhaW5zX2Jsb29kX3Byb2R1Y3RzPSLlkKYiLAogICAgc3VibWl0X21vZGU9ImRyeV9ydW4iLAogICAgZ29vZHNfbGluZXM9WwogICAgICAgIENvbnRyYWN0R29vZHNMaW5lKAogICAgICAgICAgICBvcGVyYXRpb25fY29kZT0iQUREIiwgZ29vZHNfY29kZT0iWVAtMDAxIiwKICAgICAgICAgICAgZ29vZHNfbmFtZT0i5rWL6K+V6I2v5ZOBQSIsIGFwcHJvdmFsX25vPSLlm73oja/lh4blrZdIMDAwMDAwMDEiLAogICAgICAgICAgICBzcGVjPSI1MG1nKjMw54mHIiwgdW5pdD0i55uSIiwgcXVhbnRpdHk9MTAuMCwKICAgICAgICApLAogICAgXSwKICAgIG1lcmdlZF9wZGZfcGF0aD1Ob25lLAopCgpwcmludChmIltPS10g5LiK5LiL5paH5bCx57uqOiBzb3VyY2U9e3NlbGYuX2N0eC5zb3VyY2VfdHlwZX0iKQpwcmludChmIltPS10g5qih5p2/OiB7c2VsZi5fY3R4LnRlbXBsYXRlX25hbWV9IikKcHJpbnQoZiJbT0tdIOi0p+WTgeihjOaVsDoge2xlbihzZWxmLl9jdHguZ29vZHNfbGluZXMpfSIpCg==\"\n}"
|
||||||
},
|
},
|
||||||
"EP_Gen": {
|
"EP_Gen": {
|
||||||
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
"EP_Gen_AFTERDELAY": "{\n \"val\": \"MA==\"\n}",
|
||||||
|
|
@ -352,7 +352,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SeqTag": "Main",
|
"SeqTag": "Main",
|
||||||
"ViewCenterPx": "866.266",
|
"ViewCenterPx": "1361.61",
|
||||||
"ViewCenterPy": "755.823",
|
"ViewCenterPy": "613.591",
|
||||||
"ViewScaling": "1.331"
|
"ViewScaling": "0.751315"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"ProInfo": {
|
"ProInfo": {
|
||||||
"CodeCustomCount": "0",
|
"CodeCustomCount": "0",
|
||||||
"ProAbsolutePath": "D:/RPA/AutoRpa",
|
"ProAbsolutePath": "D:/RPA/AutoRpa",
|
||||||
"ProChange": "2026/08/12 13:57:25",
|
"ProChange": "2026/08/13 10:38:45",
|
||||||
"ProColorRef": "4",
|
"ProColorRef": "4",
|
||||||
"ProCreate": "2026/08/12 10:13:31",
|
"ProCreate": "2026/08/12 10:13:31",
|
||||||
"ProDesc": "",
|
"ProDesc": "",
|
||||||
|
|
|
||||||
75
build_seq.py
75
build_seq.py
|
|
@ -1,23 +1,16 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
"""
|
"""
|
||||||
构建 Main.seq - 合同建单流程(多节点可视化 + 真实浏览器)
|
构建 Main.seq - 合同建单流程(Chrome浏览器版本)
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
def b64_apa(val):
|
def b64_apa(val):
|
||||||
"""将值编码为 APA val 格式"""
|
|
||||||
return json.dumps({"val": base64.b64encode(str(val).encode()).decode()}, indent=4)
|
return json.dumps({"val": base64.b64encode(str(val).encode()).decode()}, indent=4)
|
||||||
|
|
||||||
def b64_raw(val):
|
def b64_raw(val):
|
||||||
"""纯 base64 编码"""
|
|
||||||
return base64.b64encode(str(val).encode()).decode()
|
return base64.b64encode(str(val).encode()).decode()
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 各步骤代码
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
# S01: 初始化环境
|
|
||||||
s01_body = """\
|
s01_body = """\
|
||||||
import sys, os, json, time
|
import sys, os, json, time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -64,38 +57,29 @@ print(f"[OK] 模板: {self._ctx.template_name}")
|
||||||
print(f"[OK] 货品行数: {len(self._ctx.goods_lines)}")
|
print(f"[OK] 货品行数: {len(self._ctx.goods_lines)}")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# S02: 打开浏览器
|
|
||||||
s02_body = """\
|
s02_body = """\
|
||||||
from ubpa.iobject.browser import Browser
|
|
||||||
from rpa_yisaiqi.contract_flow_steps import open_contract_system
|
|
||||||
|
|
||||||
print("[S02] 打开 Edge 浏览器...")
|
|
||||||
print(f" URL: {self._ctx.contract_url}")
|
|
||||||
browser = Browser(Browser.AppName.edge)
|
|
||||||
browser.open(url=self._ctx.contract_url, timeout=30)
|
|
||||||
self._browser = browser
|
|
||||||
print("[OK] 浏览器已打开")
|
|
||||||
"""
|
|
||||||
|
|
||||||
# S03: 登录 + 进入起草
|
|
||||||
s03_body = """\
|
|
||||||
from rpa_yisaiqi.contract_flow_steps import login_contract_system, enter_contract_create
|
|
||||||
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
||||||
|
|
||||||
adapter = ApaBrowserAdapter()
|
print("[S02] 启动 Chrome + 打开合同系统...")
|
||||||
adapter._browser = self._browser
|
print(f" URL: {self._ctx.contract_url}")
|
||||||
adapter._is_open = True
|
adapter = ApaBrowserAdapter(url=self._ctx.contract_url)
|
||||||
|
adapter.open_url(self._ctx.contract_url)
|
||||||
|
self._adapter = adapter
|
||||||
|
print("[OK] Chrome 已打开并导航到合同系统")
|
||||||
|
"""
|
||||||
|
|
||||||
|
s03_body = """\
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import login_contract_system, enter_contract_create
|
||||||
|
|
||||||
|
adapter = self._adapter
|
||||||
print("[S03] 登录合同系统...")
|
print("[S03] 登录合同系统...")
|
||||||
login_contract_system(adapter, self._ctx)
|
login_contract_system(adapter, self._ctx)
|
||||||
print("[OK] 登录完成")
|
print("[OK] 登录完成")
|
||||||
print("[S04] 进入合同起草...")
|
print("[S04] 进入合同起草...")
|
||||||
enter_contract_create(adapter)
|
enter_contract_create(adapter)
|
||||||
print("[OK] 已进入合同起草页面")
|
print("[OK] 已进入合同起草页面")
|
||||||
self._adapter = adapter
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# S04: 选择模板 + 签约方信息
|
|
||||||
s04_body = """\
|
s04_body = """\
|
||||||
from rpa_yisaiqi.contract_flow_steps import select_contract_template, fill_contract_signatory_info
|
from rpa_yisaiqi.contract_flow_steps import select_contract_template, fill_contract_signatory_info
|
||||||
|
|
||||||
|
|
@ -109,7 +93,6 @@ fill_contract_signatory_info(adapter, self._ctx)
|
||||||
print(f"[OK] 签约方: {self._ctx.customer_name}")
|
print(f"[OK] 签约方: {self._ctx.customer_name}")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# S05: 基础表单 + 金额日期
|
|
||||||
s05_body = """\
|
s05_body = """\
|
||||||
from rpa_yisaiqi.contract_flow_steps import fill_contract_basic_info, fill_contract_amount_and_dates
|
from rpa_yisaiqi.contract_flow_steps import fill_contract_basic_info, fill_contract_amount_and_dates
|
||||||
|
|
||||||
|
|
@ -123,7 +106,6 @@ fill_contract_amount_and_dates(adapter, self._ctx)
|
||||||
print(f"[OK] 金额={self._ctx.amount}, 结束日期={self._ctx.contract_end_date}")
|
print(f"[OK] 金额={self._ctx.amount}, 结束日期={self._ctx.contract_end_date}")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# S06: 货品明细 + 付款信息 + 提交
|
|
||||||
s06_body = """\
|
s06_body = """\
|
||||||
from rpa_yisaiqi.contract_flow_steps import (
|
from rpa_yisaiqi.contract_flow_steps import (
|
||||||
fill_contract_goods_lines, fill_contract_payment_info,
|
fill_contract_goods_lines, fill_contract_payment_info,
|
||||||
|
|
@ -160,33 +142,18 @@ else:
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 节点定义
|
|
||||||
# ============================================================
|
|
||||||
steps = [
|
steps = [
|
||||||
("S01", "S01 初始化 & 上下文构建", s01_body, "设置环境变量并构建YYC测试上下文"),
|
("S01 初始化 & 上下文构建", s01_body, "设置环境变量并构建YYC测试上下文"),
|
||||||
("S02", "S02 打开Edge浏览器", s02_body, "打开Edge浏览器并导航到合同系统"),
|
("S02 启动Chrome", s02_body, "Selenium打开Chrome浏览器并导航到合同系统"),
|
||||||
("S03", "S03-S04 登录 & 进入起草", s03_body, "登录系统并进入合同起草页面"),
|
("S03-S04 登录 & 进入起草", s03_body, "登录系统并进入合同起草页面"),
|
||||||
("S04", "S05-S06 模板选择 & 签约方", s04_body, "选择模板并填写签约方信息"),
|
("S05-S06 模板选择 & 签约方", s04_body, "选择模板并填写签约方信息"),
|
||||||
("S05", "S07-S08 基础表单 & 金额日期", s05_body, "填写合同类别、金额、日期"),
|
("S07-S08 基础表单 & 金额日期", s05_body, "填写合同类别、金额、日期"),
|
||||||
("S06", "S09-S15 货品/付款/提交", s06_body, "填写货品明细、付款信息并提交"),
|
("S09-S15 货品/付款/提交", s06_body, "填写货品明细、付款信息并提交"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def make_code_import_props(pimport, pbody):
|
|
||||||
"""创建 EP_CONTROL_IMPORT 的 JSON 字符串"""
|
|
||||||
inner = json.dumps({
|
|
||||||
"PImport": pimport,
|
|
||||||
"PBCode": pbody
|
|
||||||
}, indent=4, ensure_ascii=False)
|
|
||||||
return inner
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 构建 Main.seq
|
|
||||||
# ============================================================
|
|
||||||
nodes = []
|
nodes = []
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# 开始节点
|
|
||||||
begin_tag = "20260812100000"
|
begin_tag = "20260812100000"
|
||||||
nodes.append({
|
nodes.append({
|
||||||
"NodePanelData": {
|
"NodePanelData": {
|
||||||
|
|
@ -207,12 +174,11 @@ start_x = 500
|
||||||
start_y = 200
|
start_y = 200
|
||||||
space_y = 120
|
space_y = 120
|
||||||
|
|
||||||
for i, (sid, title, body, note) in enumerate(steps):
|
for i, (title, body, note) in enumerate(steps):
|
||||||
tag = f"20260812{200000 + i:06d}"
|
tag = f"20260812{200000 + i:06d}"
|
||||||
py = start_y + i * space_y
|
py = start_y + i * space_y
|
||||||
|
|
||||||
# Encode as raw base64 for PBCode/PImport
|
pimport_b64 = b64_raw("")
|
||||||
pimport_b64 = b64_raw("sys, os, json, pathlib, time")
|
|
||||||
pbody_b64 = b64_raw(body)
|
pbody_b64 = b64_raw(body)
|
||||||
|
|
||||||
props = {
|
props = {
|
||||||
|
|
@ -273,6 +239,5 @@ main_seq = {
|
||||||
with open(r"d:\RPA\AutoRpa\Main.seq", "w", encoding="utf-8") as f:
|
with open(r"d:\RPA\AutoRpa\Main.seq", "w", encoding="utf-8") as f:
|
||||||
json.dump(main_seq, f, ensure_ascii=False, indent=4)
|
json.dump(main_seq, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
print(f"Main.seq 已生成: {len(nodes)} 节点, {len(lines)} 连线")
|
|
||||||
for n in nodes:
|
for n in nodes:
|
||||||
print(f" [{n['NodeUI']['NodeSubType'].replace('EnumWidget_','').replace('_Code_Custom','CODE').replace('_Begin','START')}] {n['NodeUI']['NodeTitle']}")
|
print(f" [{n['NodeUI']['NodeSubType'].replace('EnumWidget_','').replace('_Code_Custom','CODE').replace('_Begin','START')}] {n['NodeUI']['NodeTitle']}")
|
||||||
|
|
|
||||||
|
|
@ -1,149 +1,22 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
# 编译日期:2026-08-12 10:13:31
|
# 编译日期:2026-08-12 10:13:31
|
||||||
# 版权所有:www.i-search.com.cn
|
# 版权所有:www.i-search.com.cn
|
||||||
#
|
|
||||||
# ============================================================================
|
|
||||||
# 合同建单 (CONTRACT_CREATE) RPA 流程 - 全局函数
|
|
||||||
# ============================================================================
|
|
||||||
# 在 APA Studio 的 Python 脚本节点中调用:
|
|
||||||
# 1. 拖一个 "Python 脚本块" 到画布
|
|
||||||
# 2. 从 codes/rpa_yisaiqi 导入需要的函数
|
|
||||||
# 3. 示例见下方 __main__ 块
|
|
||||||
#
|
|
||||||
# 流程图节点清单 (15步 + 4异常分支):
|
|
||||||
# 参考: codes/rpa_yisaiqi/flows/contract_create_flow.json
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# 确保 codes 目录在 sys.path 中,以便 APA Python 3.8 能导入 rpa_yisaiqi
|
|
||||||
_CODES_DIR = Path(__file__).resolve().parent # .../AutoRpa/codes/
|
|
||||||
if str(_CODES_DIR) not in sys.path:
|
|
||||||
sys.path.insert(0, str(_CODES_DIR))
|
|
||||||
|
|
||||||
|
|
||||||
def verify_module_imports():
|
|
||||||
"""验证所有模块可以正常导入(不依赖 APA ubpa SDK)。"""
|
|
||||||
results = {}
|
|
||||||
modules = [
|
|
||||||
("callback_client", "rpa_yisaiqi.callback_client", "StateCenterClient"),
|
|
||||||
("contract_flow_steps", "rpa_yisaiqi.contract_flow_steps", "ContractFlowContext"),
|
|
||||||
("contract_draft", "rpa_yisaiqi.contract_draft", "ContractDraft"),
|
|
||||||
("pdf_utils", "rpa_yisaiqi.pdf_utils", "ensure_merged_pdf"),
|
|
||||||
("contract_create_node", "rpa_yisaiqi.contract_create_node", "ContractCreateInput"),
|
|
||||||
]
|
|
||||||
for name, module_path, class_name in modules:
|
|
||||||
try:
|
|
||||||
mod = __import__(module_path, fromlist=[class_name])
|
|
||||||
getattr(mod, class_name)
|
|
||||||
results[name] = "OK"
|
|
||||||
except Exception as e:
|
|
||||||
results[name] = f"FAIL: {e}"
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def build_yyc_dry_run_context():
|
|
||||||
"""构建 YYC 来源的 dry-run 流程上下文(不提交合同)。"""
|
|
||||||
from rpa_yisaiqi.contract_flow_steps import ContractFlowContext, ContractGoodsLine
|
|
||||||
|
|
||||||
return ContractFlowContext(
|
|
||||||
contract_url="http://contract-test.gzmpc.com:8080/",
|
|
||||||
username="test002",
|
|
||||||
password="1",
|
|
||||||
supplier_name="测试供应商",
|
|
||||||
order_date="2026-08-12",
|
|
||||||
source_type="YYC",
|
|
||||||
template_type="NON_FORMAT",
|
|
||||||
template_name="非格式--采购协议",
|
|
||||||
customer_code="CUST-001",
|
|
||||||
customer_name="测试供应商",
|
|
||||||
contract_category="采购协议",
|
|
||||||
settlement_type="付款",
|
|
||||||
contract_background="自动化测试合同起草",
|
|
||||||
contract_nature="主合同",
|
|
||||||
amount_currency="人民币",
|
|
||||||
amount="50000.00",
|
|
||||||
contract_end_date="2026-12-31",
|
|
||||||
is_major_contract="否",
|
|
||||||
is_prepayment="否",
|
|
||||||
belong_year="2026",
|
|
||||||
payment_method="账期支付",
|
|
||||||
payment_days="30",
|
|
||||||
contract_status="正式合同",
|
|
||||||
contains_blood_products="否",
|
|
||||||
submit_mode="dry_run",
|
|
||||||
goods_lines=[
|
|
||||||
ContractGoodsLine(
|
|
||||||
operation_code="ADD", goods_code="YP-001",
|
|
||||||
goods_name="测试药品A", approval_no="国药准字H00000001",
|
|
||||||
spec="50mg*30片", unit="盒", quantity=10.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
merged_pdf_path=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_draft_from_json(json_path):
|
|
||||||
"""从 JSON 文件加载合同起草数据。"""
|
|
||||||
from rpa_yisaiqi.contract_draft import load_contract_draft
|
|
||||||
return load_contract_draft(json_path)
|
|
||||||
|
|
||||||
|
|
||||||
def run_contract_flow_with_apa_browser(browser, ctx):
|
|
||||||
"""
|
|
||||||
在 APA Studio 中使用真实的 ubpa 浏览器适配器执行流程。
|
|
||||||
|
|
||||||
browser: 实现了 BrowserAdapter 协议的对象 (如 ubpa 的 iedge/ichrome_firefox)
|
|
||||||
ctx: ContractFlowContext 实例
|
|
||||||
|
|
||||||
返回合同编号字符串(dry_run 模式返回空字符串)。
|
|
||||||
"""
|
|
||||||
from rpa_yisaiqi.contract_flow_steps import run_contract_create_flow
|
|
||||||
return run_contract_create_flow(browser, ctx)
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# APA Studio 集成说明:
|
|
||||||
#
|
|
||||||
# 方式一: Python 脚本块(推荐)
|
|
||||||
# 在 APA Studio 画布拖一个 "Python 脚本块",写入:
|
|
||||||
#
|
|
||||||
# import sys
|
|
||||||
# sys.path.insert(0, r"d:\RPA\AutoRpa\codes")
|
|
||||||
# from GlobalFun import build_yyc_dry_run_context, run_contract_flow_with_apa_browser
|
|
||||||
# from rpa_yisaiqi.contract_flow_steps import BrowserAdapter
|
|
||||||
#
|
|
||||||
# ctx = build_yyc_dry_run_context()
|
|
||||||
# # browser 由 APA Studio 提供 (如 ubpa 网页对象)
|
|
||||||
# contract_no = run_contract_flow_with_apa_browser(browser, ctx)
|
|
||||||
#
|
|
||||||
# 方式二: 原生活动拖拽 (15个节点)
|
|
||||||
# 按 codes/rpa_yisaiqi/flows/contract_create_flow.json 的顺序,
|
|
||||||
# 在画布上依次拖拽: 打开网页 → 输入文本 → 鼠标点击 → ...
|
|
||||||
# 每个节点对应 contract_flow_steps.py 中的一个函数。
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
# 测试运行或调试以上全局函数入口
|
# 测试运行或调试以上全局函数入口
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("GlobalFun 模块导入验证:")
|
pass
|
||||||
results = verify_module_imports()
|
|
||||||
for name, status in results.items():
|
|
||||||
print(f" [{status}] {name}")
|
|
||||||
|
|
||||||
print("\n构建 YYC dry-run 上下文:")
|
|
||||||
ctx = build_yyc_dry_run_context()
|
|
||||||
print(f" supplier_name={ctx.supplier_name}")
|
|
||||||
print(f" source_type={ctx.source_type}")
|
|
||||||
print(f" template_name={ctx.template_name}")
|
|
||||||
print(f" submit_mode={ctx.submit_mode}")
|
|
||||||
print(f" goods_lines={len(ctx.goods_lines)} 条")
|
|
||||||
|
|
||||||
print("\n加载 YYC JSON 示例:")
|
|
||||||
draft = load_draft_from_json(
|
|
||||||
r"d:\RPA\AutoRpa\codes\rpa_yisaiqi\examples\contract_draft_yyc.json"
|
|
||||||
)
|
|
||||||
print(f" supplier={draft.supplier_name}, source={draft.source_type}")
|
|
||||||
117
codes/Main.py
117
codes/Main.py
|
|
@ -1,5 +1,5 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
# 编译日期:2026-08-12 11:59:19
|
# 编译日期:2026-08-13 09:38:51
|
||||||
# 版权所有:www.i-search.com.cn
|
# 版权所有:www.i-search.com.cn
|
||||||
import ubpa.init_input as iinput
|
import ubpa.init_input as iinput
|
||||||
from ubpa.base_util import StdOutHook, ExceptionHandler
|
from ubpa.base_util import StdOutHook, ExceptionHandler
|
||||||
|
|
@ -43,7 +43,120 @@ class AutoRpa:
|
||||||
|
|
||||||
def Main(self):
|
def Main(self):
|
||||||
#插入代码块#1
|
#插入代码块#1
|
||||||
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200000,Title:插入代码块#1,Note:合同建单完整流程 - Edge浏览器自动化')
|
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200000,Title:插入代码块#1,Note:设置环境变量并构建YYC测试上下文')
|
||||||
|
import sys, os, json, time
|
||||||
|
from pathlib import Path
|
||||||
|
_CODES_DIR = Path(r'd:\RPA\AutoRpa\codes')
|
||||||
|
if str(_CODES_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(_CODES_DIR))
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import ContractFlowContext, ContractGoodsLine
|
||||||
|
print("=" * 60)
|
||||||
|
print("合同建单 CONTRACT_CREATE - 初始化")
|
||||||
|
print("=" * 60)
|
||||||
|
self._ctx = ContractFlowContext(
|
||||||
|
contract_url="http://contract-test.gzmpc.com:8080/",
|
||||||
|
username="test002", password="1",
|
||||||
|
supplier_name="测试供应商",
|
||||||
|
order_date="2026-08-12",
|
||||||
|
source_type="YYC", template_type="NON_FORMAT",
|
||||||
|
template_name="非格式--采购协议",
|
||||||
|
customer_code="CUST-001", customer_name="测试供应商",
|
||||||
|
contract_category="采购协议", settlement_type="付款",
|
||||||
|
contract_background="自动化测试合同起草", contract_nature="主合同",
|
||||||
|
amount_currency="人民币", amount="50000.00",
|
||||||
|
contract_end_date="2026-12-31",
|
||||||
|
is_major_contract="否", is_prepayment="否",
|
||||||
|
belong_year="2026", payment_method="账期支付",
|
||||||
|
payment_days="30", contract_status="正式合同",
|
||||||
|
contains_blood_products="否",
|
||||||
|
submit_mode="dry_run",
|
||||||
|
goods_lines=[
|
||||||
|
ContractGoodsLine(
|
||||||
|
operation_code="ADD", goods_code="YP-001",
|
||||||
|
goods_name="测试药品A", approval_no="国药准字H00000001",
|
||||||
|
spec="50mg*30片", unit="盒", quantity=10.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
merged_pdf_path=None,
|
||||||
|
)
|
||||||
|
print(f"[OK] 上下文就绪: source={self._ctx.source_type}")
|
||||||
|
print(f"[OK] 模板: {self._ctx.template_name}")
|
||||||
|
print(f"[OK] 货品行数: {len(self._ctx.goods_lines)}")
|
||||||
|
|
||||||
|
#插入代码块#1
|
||||||
|
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200001,Title:插入代码块#1,Note:Selenium打开Chrome浏览器并导航到合同系统')
|
||||||
|
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
||||||
|
print("[S02] 启动 Chrome + 打开合同系统...")
|
||||||
|
print(f" URL: {self._ctx.contract_url}")
|
||||||
|
adapter = ApaBrowserAdapter(url=self._ctx.contract_url)
|
||||||
|
adapter.open_url(self._ctx.contract_url)
|
||||||
|
self._adapter = adapter
|
||||||
|
print("[OK] Chrome 已打开并导航到合同系统")
|
||||||
|
|
||||||
|
#插入代码块#1
|
||||||
|
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200002,Title:插入代码块#1,Note:登录系统并进入合同起草页面')
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import login_contract_system, enter_contract_create
|
||||||
|
adapter = self._adapter
|
||||||
|
print("[S03] 登录合同系统...")
|
||||||
|
login_contract_system(adapter, self._ctx)
|
||||||
|
print("[OK] 登录完成")
|
||||||
|
print("[S04] 进入合同起草...")
|
||||||
|
enter_contract_create(adapter)
|
||||||
|
print("[OK] 已进入合同起草页面")
|
||||||
|
|
||||||
|
#插入代码块#1
|
||||||
|
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200003,Title:插入代码块#1,Note:选择模板并填写签约方信息')
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import select_contract_template, fill_contract_signatory_info
|
||||||
|
adapter = self._adapter
|
||||||
|
print("[S05] 选择合同模板...")
|
||||||
|
select_contract_template(adapter, self._ctx)
|
||||||
|
print(f"[OK] 模板: {self._ctx.template_name}")
|
||||||
|
print("[S06] 填写签约方信息...")
|
||||||
|
fill_contract_signatory_info(adapter, self._ctx)
|
||||||
|
print(f"[OK] 签约方: {self._ctx.customer_name}")
|
||||||
|
|
||||||
|
#插入代码块#1
|
||||||
|
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200004,Title:插入代码块#1,Note:填写合同类别、金额、日期')
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import fill_contract_basic_info, fill_contract_amount_and_dates
|
||||||
|
adapter = self._adapter
|
||||||
|
print("[S07] 填写基础表单...")
|
||||||
|
fill_contract_basic_info(adapter, self._ctx)
|
||||||
|
print(f"[OK] 类别={self._ctx.contract_category}, 结算={self._ctx.settlement_type}")
|
||||||
|
print("[S08] 填写金额与日期...")
|
||||||
|
fill_contract_amount_and_dates(adapter, self._ctx)
|
||||||
|
print(f"[OK] 金额={self._ctx.amount}, 结束日期={self._ctx.contract_end_date}")
|
||||||
|
|
||||||
|
#插入代码块#1
|
||||||
|
self.__logger.dlogs(job_no=self.job_no,logmsg='Flow:Main,StepNodeTag:20260812200005,Title:插入代码块#1,Note:填写货品明细、付款信息并提交')
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import (
|
||||||
|
fill_contract_goods_lines, fill_contract_payment_info,
|
||||||
|
upload_contract_document_if_needed, submit_contract_for_approval,
|
||||||
|
read_contract_number
|
||||||
|
)
|
||||||
|
adapter = self._adapter
|
||||||
|
print("[S09] 填写货品明细...")
|
||||||
|
fill_contract_goods_lines(adapter, self._ctx)
|
||||||
|
print(f"[OK] 货品: {len(self._ctx.goods_lines)} 行")
|
||||||
|
print("[S10] 填写付款信息...")
|
||||||
|
fill_contract_payment_info(adapter, self._ctx)
|
||||||
|
print(f"[OK] 付款方式={self._ctx.payment_method}")
|
||||||
|
if self._ctx.source_type == "YYC":
|
||||||
|
print("[S11] 合并PDF [YYC专属]... (单PDF, 跳过合并)")
|
||||||
|
print("[S12] 上传PDF附件 [YYC专属]... (dry_run 跳过)")
|
||||||
|
print()
|
||||||
|
print("[S13] 提交审批...")
|
||||||
|
if not submit_contract_for_approval(adapter, self._ctx):
|
||||||
|
print("[DRY-RUN] 演练模式,跳过实际提交")
|
||||||
|
print("=" * 60)
|
||||||
|
print("合同建单流程演练完成")
|
||||||
|
print("=" * 60)
|
||||||
|
else:
|
||||||
|
print("[S14] 读取合同编号...")
|
||||||
|
contract_no = read_contract_number(adapter)
|
||||||
|
print(f"[OK] 合同编号: {contract_no}")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"[COMPLETED] 合同创建成功: {contract_no}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,184 +1,236 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
"""
|
"""
|
||||||
APA (艺赛旗) BrowserAdapter 实现
|
APA (艺赛旗) Selenium Chrome BrowserAdapter 实现
|
||||||
将 contract_flow_steps.BrowserAdapter 协议映射到 ubpa/iedge 浏览器 API
|
通过 Selenium WebDriver 直接操控 Chrome,不依赖 APA 浏览器模块
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
import json
|
|
||||||
import time
|
import time
|
||||||
import base64
|
|
||||||
|
|
||||||
|
|
||||||
def _make_apa_attr_map(css_selector: str) -> str:
|
|
||||||
"""将 CSS 选择器转换为 APA attrMap JSON 格式"""
|
|
||||||
hierarchy = {
|
|
||||||
"hasReachedRelativeAncestor": "false",
|
|
||||||
"nodeHierarchyInfo": [
|
|
||||||
{
|
|
||||||
"isPresentInSelector": 1,
|
|
||||||
"otherAttributes": {},
|
|
||||||
"selectorInfo": {
|
|
||||||
"attributes": {
|
|
||||||
"css-selector": css_selector,
|
|
||||||
"tag": "",
|
|
||||||
"xpath": ""
|
|
||||||
},
|
|
||||||
"index": 0,
|
|
||||||
"tagName": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return json.dumps(hierarchy, ensure_ascii=False)
|
|
||||||
|
|
||||||
|
|
||||||
class ApaBrowserAdapter:
|
class ApaBrowserAdapter:
|
||||||
"""APA Studio Edge 浏览器适配器,实现 BrowserAdapter 协议"""
|
"""Selenium Chrome 浏览器适配器,实现 BrowserAdapter 协议"""
|
||||||
|
|
||||||
def __init__(self, url: str = ""):
|
def __init__(self, url: str = ""):
|
||||||
from ubpa.iobject.browser import Browser
|
|
||||||
self._browser = Browser(Browser.AppName.edge)
|
|
||||||
self._url = url
|
self._url = url
|
||||||
|
self._driver = None
|
||||||
self._is_open = False
|
self._is_open = False
|
||||||
|
|
||||||
|
def _get_driver(self):
|
||||||
|
"""创建或获取 Chrome WebDriver"""
|
||||||
|
if self._driver is not None:
|
||||||
|
return self._driver
|
||||||
|
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.service import Service
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
from webdriver_manager.chrome import ChromeDriverManager
|
||||||
|
|
||||||
|
options = Options()
|
||||||
|
# 不自动关闭浏览器,方便调试
|
||||||
|
options.add_experimental_option("detach", True)
|
||||||
|
# 避免被检测为自动化
|
||||||
|
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||||
|
options.add_experimental_option("useAutomationExtension", False)
|
||||||
|
# 基本参数
|
||||||
|
options.add_argument("--disable-blink-features=AutomationControlled")
|
||||||
|
options.add_argument("--no-first-run")
|
||||||
|
options.add_argument("--no-default-browser-check")
|
||||||
|
|
||||||
|
service = Service(ChromeDriverManager().install())
|
||||||
|
self._driver = webdriver.Chrome(service=service, options=options)
|
||||||
|
return self._driver
|
||||||
|
|
||||||
def _ensure_open(self):
|
def _ensure_open(self):
|
||||||
if not self._is_open:
|
if self._driver is None:
|
||||||
from ubpa.iobject.browser import Browser
|
driver = self._get_driver()
|
||||||
self._browser.open(url=self._url, timeout=30)
|
if self._url:
|
||||||
|
driver.get(self._url)
|
||||||
self._is_open = True
|
self._is_open = True
|
||||||
|
|
||||||
|
# ---- 内部查找方法 ----
|
||||||
|
|
||||||
|
def _find(self, selector: str, timeout: int = 5):
|
||||||
|
"""
|
||||||
|
智能查找元素:支持 CSS、XPath、text=、label=、aria=、title=、:contains()
|
||||||
|
"""
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
|
import re
|
||||||
|
|
||||||
|
# 1. text=xxx → 查找包含对应文本的元素
|
||||||
|
if selector.startswith("text="):
|
||||||
|
text_val = selector[5:].strip('"').strip("'")
|
||||||
|
xpath = f'//*[contains(text(),"{text_val}")]'
|
||||||
|
return self._driver.find_element(By.XPATH, xpath)
|
||||||
|
|
||||||
|
# 2. label=xxx → 找到对应标签的输入框
|
||||||
|
if selector.startswith("label="):
|
||||||
|
text_val = selector[6:].strip('"').strip("'")
|
||||||
|
xpath = f'//label[contains(text(),"{text_val}")]/following-sibling::*[1][self::input or self::select or self::textarea]'
|
||||||
|
try:
|
||||||
|
return self._driver.find_element(By.XPATH, xpath)
|
||||||
|
except NoSuchElementException:
|
||||||
|
# Fallback: find the label's "for" attribute
|
||||||
|
xpath2 = f'//label[contains(text(),"{text_val}")]/@for'
|
||||||
|
for_id = self._driver.find_element(By.XPATH, xpath2).get_attribute("for") if False else None
|
||||||
|
if for_id:
|
||||||
|
return self._driver.find_element(By.ID, for_id)
|
||||||
|
raise
|
||||||
|
|
||||||
|
# 3. aria=xxx → aria-label 属性
|
||||||
|
if selector.startswith("aria="):
|
||||||
|
text_val = selector[5:].strip('"').strip("'")
|
||||||
|
return self._driver.find_element(By.CSS_SELECTOR, f'[aria-label="{text_val}"]')
|
||||||
|
|
||||||
|
# 4. title=xxx → title 属性
|
||||||
|
if selector.startswith("title="):
|
||||||
|
text_val = selector[6:].strip('"').strip("'")
|
||||||
|
return self._driver.find_element(By.CSS_SELECTOR, f'[title="{text_val}"]')
|
||||||
|
|
||||||
|
# 5. :contains() → 转 XPath
|
||||||
|
if ":contains(" in selector:
|
||||||
|
m = re.match(r'(.+?):contains\("(.+?)"\)', selector)
|
||||||
|
if m:
|
||||||
|
tag_part, text_val = m.groups()
|
||||||
|
xpath = f'//{tag_part}[contains(.,"{text_val}")]'
|
||||||
|
return self._driver.find_element(By.XPATH, xpath)
|
||||||
|
|
||||||
|
# 6. 显式 XPath(以 / 或 // 开头)优先按 XPath 查找
|
||||||
|
if selector.startswith("//") or selector.startswith("/"):
|
||||||
|
return self._driver.find_element(By.XPATH, selector)
|
||||||
|
|
||||||
|
# 7. 标准 CSS selector
|
||||||
|
return self._driver.find_element(By.CSS_SELECTOR, selector)
|
||||||
|
|
||||||
# ---- BrowserAdapter 协议实现 ----
|
# ---- BrowserAdapter 协议实现 ----
|
||||||
|
|
||||||
def open_url(self, url: str) -> None:
|
def open_url(self, url: str) -> None:
|
||||||
self._url = url
|
self._url = url
|
||||||
self._ensure_open()
|
self._get_driver().get(url)
|
||||||
|
self._is_open = True
|
||||||
|
|
||||||
def fill_first(self, selectors: list[str], value: str) -> None:
|
def _set_value_via_js(self, el, value: str) -> None:
|
||||||
|
"""通过原生 value setter + 事件注入设置值,兼容 LUI/React 受控组件"""
|
||||||
|
self._driver.execute_script(
|
||||||
|
"""
|
||||||
|
var el = arguments[0];
|
||||||
|
var value = arguments[1];
|
||||||
|
var proto = el.tagName === 'TEXTAREA'
|
||||||
|
? window.HTMLTextAreaElement.prototype
|
||||||
|
: window.HTMLInputElement.prototype;
|
||||||
|
var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
|
||||||
|
setter.call(el, value);
|
||||||
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
el.dispatchEvent(new Event('blur', { bubbles: true }));
|
||||||
|
""",
|
||||||
|
el,
|
||||||
|
str(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
def fill_first(self, selectors: list, value: str) -> None:
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
import ubpa.iedge as iedge
|
|
||||||
last_error = None
|
last_error = None
|
||||||
for sel in selectors:
|
for sel in selectors:
|
||||||
try:
|
try:
|
||||||
attr_map = _make_apa_attr_map(sel)
|
el = self._find(sel)
|
||||||
iedge.set_element_val(
|
# 先尝试原生输入;LUI 受控组件用 JS 注入兜底
|
||||||
attrMap=attr_map, title="*", url="*",
|
try:
|
||||||
text=str(value), waitfor=5, program="msedge.exe"
|
el.clear()
|
||||||
)
|
el.send_keys(str(value))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if el.get_attribute("value") != str(value):
|
||||||
|
self._set_value_via_js(el, str(value))
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
continue
|
continue
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"fill_first failed: {selectors}, last: {last_error}")
|
||||||
f"fill_first failed for all selectors {selectors}: {last_error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def click_first(self, selectors: list[str]) -> None:
|
def click_first(self, selectors: list) -> None:
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
import ubpa.iedge as iedge
|
|
||||||
last_error = None
|
last_error = None
|
||||||
for sel in selectors:
|
for sel in selectors:
|
||||||
try:
|
try:
|
||||||
attr_map = _make_apa_attr_map(sel)
|
el = self._find(sel)
|
||||||
iedge.do_click_pos(
|
# LUI/委托事件用 JS 点击最可靠
|
||||||
attrMap=attr_map, title="*", url="*",
|
self._driver.execute_script("arguments[0].click()", el)
|
||||||
waitfor=5, program="msedge.exe"
|
|
||||||
)
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
continue
|
continue
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"click_first failed: {selectors}, last: {last_error}")
|
||||||
f"click_first failed for all selectors {selectors}: {last_error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None:
|
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None:
|
||||||
import ubpa.iedge as iedge
|
|
||||||
elapsed = 0
|
|
||||||
interval = 1
|
|
||||||
while elapsed < timeout_seconds:
|
|
||||||
try:
|
|
||||||
# 尝试获取页面文本匹配
|
|
||||||
result = iedge.get_element_val(
|
|
||||||
attrMap=_make_apa_attr_map(f"text={text}"),
|
|
||||||
title="*", url="*", waitfor=3, program="msedge.exe"
|
|
||||||
)
|
|
||||||
if result:
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
time.sleep(interval)
|
|
||||||
elapsed += interval
|
|
||||||
raise TimeoutError(f"wait_for_text '{text}' timed out after {timeout_seconds}s")
|
|
||||||
|
|
||||||
def select_first(self, selectors: list[str], value: str) -> None:
|
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
import ubpa.iedge as iedge
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
try:
|
||||||
|
WebDriverWait(self._driver, timeout_seconds).until(
|
||||||
|
EC.text_to_be_present_in_element((By.TAG_NAME, "body"), text)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise TimeoutError(f"wait_for_text '{text}' timed out: {e}")
|
||||||
|
|
||||||
|
def select_first(self, selectors: list, value: str) -> None:
|
||||||
|
self._ensure_open()
|
||||||
|
from selenium.webdriver.support.ui import Select
|
||||||
last_error = None
|
last_error = None
|
||||||
for sel in selectors:
|
for sel in selectors:
|
||||||
try:
|
try:
|
||||||
attr_map = _make_apa_attr_map(sel)
|
el = self._find(sel)
|
||||||
iedge.set_element_selected_item(
|
Select(el).select_by_visible_text(value)
|
||||||
attrMap=attr_map, title="*", url="*",
|
|
||||||
itemText=value, itemMode="text",
|
|
||||||
waitfor=5, program="msedge.exe"
|
|
||||||
)
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
continue
|
continue
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"select_first failed: {selectors}, last: {last_error}")
|
||||||
f"select_first failed for all selectors {selectors}: {last_error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def upload_file_first(self, selectors: list[str], file_path: str) -> None:
|
def upload_file_first(self, selectors: list, file_path: str) -> None:
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
import ubpa.iedge as iedge
|
|
||||||
last_error = None
|
last_error = None
|
||||||
for sel in selectors:
|
for sel in selectors:
|
||||||
try:
|
try:
|
||||||
attr_map = _make_apa_attr_map(sel)
|
el = self._find(sel)
|
||||||
iedge.set_element_val(
|
el.send_keys(file_path)
|
||||||
attrMap=attr_map, title="*", url="*",
|
|
||||||
text=file_path, waitfor=5, program="msedge.exe"
|
|
||||||
)
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
continue
|
continue
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"upload_file_first failed: {selectors}, last: {last_error}")
|
||||||
f"upload_file_first failed for all selectors {selectors}: {last_error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_first(self, selectors: list[str], value_name: str = "") -> str:
|
def read_first(self, selectors: list, value_name: str = "") -> str:
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
import ubpa.iedge as iedge
|
|
||||||
last_error = None
|
last_error = None
|
||||||
for sel in selectors:
|
for sel in selectors:
|
||||||
try:
|
try:
|
||||||
attr_map = _make_apa_attr_map(sel)
|
el = self._find(sel)
|
||||||
result = iedge.get_element_val(
|
result = el.text or el.get_attribute("value") or ""
|
||||||
attrMap=attr_map, title="*", url="*",
|
|
||||||
waitfor=5, program="msedge.exe"
|
|
||||||
)
|
|
||||||
if result:
|
if result:
|
||||||
return str(result)
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
continue
|
continue
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"read_first failed: {selectors}, last: {last_error}")
|
||||||
f"read_first failed for all selectors {selectors}: {last_error}"
|
|
||||||
)
|
def read_body_text(self) -> str:
|
||||||
|
self._ensure_open()
|
||||||
|
try:
|
||||||
|
return self._driver.find_element("tag name", "body").text
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
def screenshot(self, name: str) -> str:
|
def screenshot(self, name: str) -> str:
|
||||||
import os
|
import os
|
||||||
save_dir = os.path.join(os.path.dirname(__file__), "screenshots")
|
save_dir = os.path.join(os.path.dirname(__file__), "screenshots")
|
||||||
os.makedirs(save_dir, exist_ok=True)
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
path = os.path.join(save_dir, f"{name}.png")
|
path = os.path.join(save_dir, f"{name}.png")
|
||||||
# APA browser screenshot capability - placeholder
|
if self._driver:
|
||||||
print(f" [screenshot] {name} -> {path}")
|
self._driver.save_screenshot(path)
|
||||||
return path
|
return path
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from urllib.error import HTTPError
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
JsonDict = dict
|
JsonDict = dict[str, Any]
|
||||||
Opener = Callable[[Request, float], Any]
|
Opener = Callable[[Request, float], Any]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,56 @@ def run_contract_create_flow(browser: BrowserAdapter, ctx: ContractFlowContext)
|
||||||
return read_contract_number(browser)
|
return read_contract_number(browser)
|
||||||
|
|
||||||
|
|
||||||
|
# 合同起草页(模板选择)直达 URL,绕过 SPA 菜单/iframe 复杂性
|
||||||
|
DRAFT_PAGE_URL = (
|
||||||
|
"http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/"
|
||||||
|
"draft.jsp?isSimpleCategory=false"
|
||||||
|
"&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply"
|
||||||
|
"&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 非格式-采购协议(2025集团版)模板 ID(测试环境固定值)
|
||||||
|
# 直接跳转表单页,绕过「点模板」这一依赖前端委托事件、Selenium 点击无效的步骤
|
||||||
|
TEMPLATE_ID = "198d10b04325279b8ffeb8b4c6493b5a"
|
||||||
|
FORM_PAGE_URL = (
|
||||||
|
"http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/"
|
||||||
|
"kmAgreementApply.do?method=add"
|
||||||
|
f"&fdTemplateId={TEMPLATE_ID}"
|
||||||
|
f"&.fdTemplate={TEMPLATE_ID}"
|
||||||
|
f"&i.docTemplate={TEMPLATE_ID}"
|
||||||
|
"&s_css=default"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Landray/LUI 表单字段的真实 name
|
||||||
|
F = {
|
||||||
|
"customer_code": 'input[name="extendDataFormInfo.value(fd_customeropcode)"]',
|
||||||
|
"customer_name": 'input[name="extendDataFormInfo.value(fd_customername)"]',
|
||||||
|
"contract_category": 'input[name="extendDataFormInfo.value(fd_contract_cate_type)"]',
|
||||||
|
"settlement_type": 'select#fdSettype',
|
||||||
|
"contract_background": 'textarea[name="extendDataFormInfo.value(fd_desc_info)"]',
|
||||||
|
"nature_main": 'input[name="fdType"][value="00"]',
|
||||||
|
"amount": 'input[name="fdAccount"]',
|
||||||
|
"sign_date": 'input[name="fdSigndate"]',
|
||||||
|
"begin_date": 'input[name="fdEffedate"]',
|
||||||
|
"end_date": 'input[name="fdEnddate"]',
|
||||||
|
"is_major_contract": 'input[name="extendDataFormInfo.value(fd_is_major_contract)"]',
|
||||||
|
"is_prepayment": 'input[name="extendDataFormInfo.value(fd_advance_amout)"]',
|
||||||
|
"belong_year": 'input[name="extendDataFormInfo.value(fd_contract_year_text)"]',
|
||||||
|
"payment_method": 'input[name="extendDataFormInfo.value(fd_paytype)"]',
|
||||||
|
"payment_days": 'input[name="extendDataFormInfo.value(fd_pay_time_limit)"]',
|
||||||
|
"contract_status": 'input[name="extendDataFormInfo.value(fd_suconstatus)"]',
|
||||||
|
"contains_blood": 'input[name="extendDataFormInfo.value(fd_3d1ce3953f1162)"]',
|
||||||
|
"goods_code": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_opcode)"]',
|
||||||
|
"goods_name": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsname)"]',
|
||||||
|
"goods_mdm": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_mdmCode)"]',
|
||||||
|
"goods_approval": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_registdocno)"]',
|
||||||
|
"goods_spec": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodstype)"]',
|
||||||
|
"goods_unit": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)"]',
|
||||||
|
"goods_qty": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)"]',
|
||||||
|
"goods_price": 'input[name="extendDataFormInfo.value(fd_detail1.0.fd_unitprice)"]',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
browser.open_url(ctx.contract_url)
|
browser.open_url(ctx.contract_url)
|
||||||
|
|
||||||
|
|
@ -85,100 +135,84 @@ def open_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> N
|
||||||
def login_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def login_contract_system(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
browser.fill_first(['input[name="j_username"]', 'input[placeholder="用户名"]'], ctx.username)
|
browser.fill_first(['input[name="j_username"]', 'input[placeholder="用户名"]'], ctx.username)
|
||||||
browser.fill_first(['input[name="j_password"]', 'input[placeholder="密码"]'], ctx.password)
|
browser.fill_first(['input[name="j_password"]', 'input[placeholder="密码"]'], ctx.password)
|
||||||
browser.click_first(['input[name="btn_submit"]', 'a[href*="btn_submit"]', 'text=登录'])
|
browser.click_first(['a[class*="login_button"]', 'a:contains("登录")', 'input[name="btn_submit"]'])
|
||||||
browser.wait_for_text("合同", timeout_seconds=20)
|
browser.wait_for_text("合同", timeout_seconds=20)
|
||||||
|
|
||||||
|
|
||||||
def enter_contract_create(browser: BrowserAdapter) -> None:
|
def enter_contract_create(browser: BrowserAdapter) -> None:
|
||||||
browser.click_first(["text=合同管理", "aria=合同管理"])
|
# 登录后直接导航到起草表单页,跳过模板选择(前端委托事件 Selenium 点击无效)
|
||||||
browser.click_first(["text=合同起草", "aria=合同起草"])
|
browser.open_url(FORM_PAGE_URL)
|
||||||
browser.wait_for_text("最近使用模板", timeout_seconds=20)
|
browser.wait_for_text("客户编码", timeout_seconds=20)
|
||||||
|
|
||||||
|
|
||||||
def select_contract_template(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def select_contract_template(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
template_text = ctx.template_name or (
|
# 已通过 URL 直接指定模板,本步骤仅兜底:若尚未进入表单则再次导航
|
||||||
"非格式--采购协议" if ctx.template_type == "NON_FORMAT" else "购销协议书(2025)"
|
if "客户编码" not in browser.read_body_text():
|
||||||
)
|
browser.open_url(FORM_PAGE_URL)
|
||||||
browser.click_first([f"text={template_text}", f"text={template_text}...", f"title={template_text}"])
|
browser.wait_for_text("客户编码", timeout_seconds=20)
|
||||||
browser.wait_for_text("合同起草", timeout_seconds=20)
|
|
||||||
|
|
||||||
|
|
||||||
def fill_contract_signatory_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def fill_contract_signatory_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
customer_name = ctx.customer_name or ctx.supplier_name
|
customer_name = ctx.customer_name or ctx.supplier_name
|
||||||
if ctx.customer_code:
|
if ctx.customer_code:
|
||||||
browser.fill_first(
|
browser.fill_first([F["customer_code"]], ctx.customer_code)
|
||||||
['label=客户编码', 'input[name="fdCustomerCode"]', 'input[placeholder*="客户编码"]'],
|
|
||||||
ctx.customer_code,
|
|
||||||
)
|
|
||||||
if customer_name:
|
if customer_name:
|
||||||
browser.fill_first(
|
browser.fill_first([F["customer_name"]], customer_name)
|
||||||
['label=客户名称', 'input[name="fdCustomerName"]', 'input[placeholder*="客户名称"]'],
|
|
||||||
customer_name,
|
|
||||||
)
|
|
||||||
browser.screenshot("contract-signatory-info-filled")
|
browser.screenshot("contract-signatory-info-filled")
|
||||||
|
|
||||||
|
|
||||||
def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
browser.select_first(['label=合同版本类型', 'select[name="fdTemplateType"]'], contract_version_label(ctx))
|
|
||||||
if ctx.contract_category:
|
if ctx.contract_category:
|
||||||
browser.select_first(['label=合同类别', 'select[name="fdContractCategory"]'], ctx.contract_category)
|
browser.fill_first([F["contract_category"]], ctx.contract_category)
|
||||||
browser.select_first(['label=结算类型', 'select[name="fdSettlementType"]'], ctx.settlement_type)
|
if ctx.settlement_type:
|
||||||
|
browser.select_first([F["settlement_type"]], ctx.settlement_type)
|
||||||
if ctx.contract_background:
|
if ctx.contract_background:
|
||||||
browser.fill_first(
|
browser.fill_first([F["contract_background"]], ctx.contract_background)
|
||||||
['label=合同背景概述', 'textarea[name="fdContractBackground"]'],
|
# 合同性质默认主合同(页面已勾选),仅在需要从合同时切换
|
||||||
ctx.contract_background,
|
if ctx.contract_nature and ctx.contract_nature != "主合同":
|
||||||
)
|
browser.click_first(['input[name="fdType"][value="01"]'])
|
||||||
browser.click_first([f"label={ctx.contract_nature}", f"text={ctx.contract_nature}"])
|
|
||||||
browser.screenshot("contract-basic-info-filled")
|
browser.screenshot("contract-basic-info-filled")
|
||||||
|
|
||||||
|
|
||||||
def fill_contract_amount_and_dates(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def fill_contract_amount_and_dates(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
browser.select_first(['label=合同金额', 'select[name="fdCurrency"]'], ctx.amount_currency)
|
|
||||||
if ctx.amount:
|
if ctx.amount:
|
||||||
browser.fill_first(['label=金额(元)', 'input[name="fdAmount"]'], ctx.amount)
|
browser.fill_first([F["amount"]], ctx.amount)
|
||||||
browser.fill_first(
|
browser.fill_first([F["sign_date"]], ctx.order_date)
|
||||||
['label=合同签订日期', 'input[name="fdSignDate"]', 'input[placeholder*="合同签订"]'],
|
browser.fill_first([F["begin_date"]], ctx.order_date)
|
||||||
ctx.order_date,
|
|
||||||
)
|
|
||||||
browser.fill_first(
|
|
||||||
['label=合同开始日期', 'input[name="fdStartDate"]', 'input[placeholder*="合同开始"]'],
|
|
||||||
ctx.order_date,
|
|
||||||
)
|
|
||||||
if ctx.contract_end_date:
|
if ctx.contract_end_date:
|
||||||
browser.fill_first(
|
browser.fill_first([F["end_date"]], ctx.contract_end_date)
|
||||||
['label=合同结束日期', 'input[name="fdEndDate"]', 'input[placeholder*="合同结束"]'],
|
|
||||||
ctx.contract_end_date,
|
|
||||||
)
|
|
||||||
browser.screenshot("contract-amount-and-dates-filled")
|
browser.screenshot("contract-amount-and-dates-filled")
|
||||||
|
|
||||||
|
|
||||||
def fill_contract_goods_lines(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def fill_contract_goods_lines(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
for index, line in enumerate(ctx.goods_lines, start=1):
|
for index, line in enumerate(ctx.goods_lines, start=1):
|
||||||
if index > 1:
|
if index > 1:
|
||||||
browser.click_first(["text=新增明细", "text=添加货品", "text=新增行"])
|
browser.click_first(["text=添加行", "text=新增明细", "text=新增行"])
|
||||||
prefix = f"明细{index}"
|
browser.fill_first([F["goods_code"]], line.goods_code)
|
||||||
browser.fill_first([f'label={prefix}操作码', 'input[name="operationCode"]'], line.operation_code)
|
browser.fill_first([F["goods_name"]], line.goods_name)
|
||||||
browser.fill_first([f'label={prefix}货品编码', 'input[name="goodsCode"]'], line.goods_code)
|
if line.approval_no:
|
||||||
browser.fill_first([f'label={prefix}名称', 'input[name="goodsName"]'], line.goods_name)
|
browser.fill_first([F["goods_approval"]], line.approval_no)
|
||||||
browser.fill_first([f'label={prefix}国药准字', 'input[name="approvalNo"]'], line.approval_no)
|
if line.spec:
|
||||||
browser.fill_first([f'label={prefix}规格', 'input[name="spec"]'], line.spec)
|
browser.fill_first([F["goods_spec"]], line.spec)
|
||||||
browser.fill_first([f'label={prefix}单位', 'input[name="unit"]'], line.unit)
|
if line.unit:
|
||||||
browser.fill_first([f'label={prefix}数量', 'input[name="quantity"]'], str(line.quantity))
|
browser.fill_first([F["goods_unit"]], line.unit)
|
||||||
|
browser.fill_first([F["goods_qty"]], str(line.quantity))
|
||||||
browser.screenshot("contract-goods-lines-filled")
|
browser.screenshot("contract-goods-lines-filled")
|
||||||
|
|
||||||
|
|
||||||
def fill_contract_payment_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def fill_contract_payment_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
browser.select_first(['label=是否重大合同', 'select[name="fdIsMajorContract"]'], ctx.is_major_contract)
|
browser.fill_first([F["is_major_contract"]], ctx.is_major_contract)
|
||||||
browser.select_first(['label=是否预付款', 'select[name="fdIsPrepayment"]'], ctx.is_prepayment)
|
browser.fill_first([F["is_prepayment"]], ctx.is_prepayment)
|
||||||
if ctx.belong_year:
|
if ctx.belong_year:
|
||||||
browser.fill_first(['label=合同归属年份', 'input[name="fdBelongYear"]'], ctx.belong_year)
|
browser.fill_first([F["belong_year"]], ctx.belong_year)
|
||||||
browser.select_first(['label=付款方式', 'select[name="fdPaymentMethod"]', 'select[name="paymentMethod"]'], ctx.payment_method)
|
if ctx.payment_method:
|
||||||
browser.fill_first(['label=付款期限', 'input[name="fdPaymentDays"]', 'input[name="paymentDays"]'], ctx.payment_days)
|
browser.fill_first([F["payment_method"]], ctx.payment_method)
|
||||||
browser.select_first(['label=合同状态', 'select[name="fdContractStatus"]'], ctx.contract_status)
|
if ctx.payment_days:
|
||||||
browser.select_first(
|
browser.fill_first([F["payment_days"]], ctx.payment_days)
|
||||||
['label=是否包含血液制品', 'select[name="fdContainsBloodProducts"]'],
|
if ctx.contract_status:
|
||||||
ctx.contains_blood_products,
|
browser.fill_first([F["contract_status"]], ctx.contract_status)
|
||||||
)
|
if ctx.contains_blood_products:
|
||||||
|
browser.fill_first([F["contains_blood"]], ctx.contains_blood_products)
|
||||||
browser.screenshot("contract-payment-filled")
|
browser.screenshot("contract-payment-filled")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""诊断:登录 → 起草页 → 点模板,打印点击前后 URL 和页面文本。"""
|
||||||
|
import os, sys, time
|
||||||
|
|
||||||
|
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PARENT = os.path.dirname(BASE)
|
||||||
|
if PARENT not in sys.path:
|
||||||
|
sys.path.insert(0, PARENT)
|
||||||
|
|
||||||
|
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
||||||
|
|
||||||
|
DRAFT = ("http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/"
|
||||||
|
"draft.jsp?isSimpleCategory=false"
|
||||||
|
"&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply"
|
||||||
|
"&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl")
|
||||||
|
|
||||||
|
a = ApaBrowserAdapter("http://contract-test.gzmpc.com:8080/")
|
||||||
|
a.open_url("http://contract-test.gzmpc.com:8080/")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 登录
|
||||||
|
a.fill_first(['input[name="j_username"]'], "test002")
|
||||||
|
a.fill_first(['input[name="j_password"]'], "1")
|
||||||
|
a.click_first(['a[class*="login_button"]'])
|
||||||
|
time.sleep(2)
|
||||||
|
print("登录后 URL:", a._driver.current_url)
|
||||||
|
|
||||||
|
# 进起草页
|
||||||
|
a.open_url(DRAFT)
|
||||||
|
time.sleep(3)
|
||||||
|
print("起草页 URL:", a._driver.current_url)
|
||||||
|
print("起草页是否有 最近使用模板:", "最近使用模板" in a._driver.find_element("tag name", "body").text)
|
||||||
|
|
||||||
|
# 找模板元素
|
||||||
|
try:
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
el = a._driver.find_element(By.XPATH, '//p[contains(@class,"title")][contains(@title,"非格式-采购协议")]')
|
||||||
|
print("找到模板元素:", el.get_attribute("title"))
|
||||||
|
print("元素可见:", el.is_displayed(), "位置:", el.location, "大小:", el.size)
|
||||||
|
|
||||||
|
# 尝试原生 click
|
||||||
|
print("尝试原生 click ...")
|
||||||
|
el.click()
|
||||||
|
time.sleep(3)
|
||||||
|
print("原生 click 后 URL:", a._driver.current_url)
|
||||||
|
print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text)
|
||||||
|
except Exception as e:
|
||||||
|
print("错误:", e)
|
||||||
|
|
||||||
|
# 尝试 JS click
|
||||||
|
print("尝试 JS click ...")
|
||||||
|
try:
|
||||||
|
a._driver.execute_script("arguments[0].click()", el)
|
||||||
|
time.sleep(3)
|
||||||
|
print("JS click 后 URL:", a._driver.current_url)
|
||||||
|
print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text)
|
||||||
|
except Exception as e:
|
||||||
|
print("JS click 错误:", e)
|
||||||
|
|
||||||
|
# 尝试 mousedown/mouseup/click 完整事件链
|
||||||
|
print("尝试完整事件链 click ...")
|
||||||
|
try:
|
||||||
|
a._driver.execute_script("""
|
||||||
|
var el = arguments[0];
|
||||||
|
var rect = el.getBoundingClientRect();
|
||||||
|
var cx = rect.left + rect.width/2, cy = rect.top + rect.height/2;
|
||||||
|
['mousedown','mouseup','click'].forEach(function(type){
|
||||||
|
el.dispatchEvent(new MouseEvent(type, {bubbles:true, cancelable:true, clientX:cx, clientY:cy}));
|
||||||
|
});
|
||||||
|
""", el)
|
||||||
|
time.sleep(3)
|
||||||
|
print("事件链 click 后 URL:", a._driver.current_url)
|
||||||
|
print("body 含 客户编码:", "客户编码" in a._driver.find_element("tag name", "body").text)
|
||||||
|
except Exception as e:
|
||||||
|
print("事件链 click 错误:", e)
|
||||||
|
|
||||||
|
print("DONE")
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
|
|
@ -1,116 +1,42 @@
|
||||||
{
|
{
|
||||||
"knownLoginSelectors": {
|
"login": {
|
||||||
"username": "input[name='j_username']",
|
"username": "input[name='j_username']",
|
||||||
"password": "input[name='j_password']",
|
"password": "input[name='j_password']",
|
||||||
"submit": "input[name='btn_submit']"
|
"submit": "input[name='btn_submit']",
|
||||||
|
"submitLink": "text=登录"
|
||||||
},
|
},
|
||||||
"candidateSelectors": {
|
"navigation": {
|
||||||
"contractManagementMenu": [
|
"draftPage": "http://contract-test.gzmpc.com:8080/km/agreement/km_agreement_apply/draft/draft.jsp?isSimpleCategory=false&mainModelName=com.landray.kmss.km.agreement.model.KmAgreementApply&modelName=com.landray.kmss.km.agreement.model.KmAgreementTmpl",
|
||||||
"text=合同管理",
|
"templatePrefix": "非格式-采购协议"
|
||||||
"aria=合同管理"
|
},
|
||||||
],
|
"form": {
|
||||||
"contractDraftMenu": [
|
"customerCode": "input[name='extendDataFormInfo.value(fd_customeropcode)']",
|
||||||
"text=合同起草",
|
"customerName": "input[name='extendDataFormInfo.value(fd_customername)']",
|
||||||
"aria=合同起草"
|
"contractCategory": "input[name='extendDataFormInfo.value(fd_contract_cate_type)']",
|
||||||
],
|
"settlementType": "select#fdSettype",
|
||||||
"recentNonFormatPurchaseTemplate": [
|
"contractBackground": "textarea[name='extendDataFormInfo.value(fd_desc_info)']",
|
||||||
"text=非格式--采购协议",
|
"contractNatureMain": "input[name='fdType'][value='00']",
|
||||||
"text=非格式--采购协议...",
|
"contractNatureSub": "input[name='fdType'][value='01']",
|
||||||
"title=非格式--采购协议"
|
"amount": "input[name='fdAccount']",
|
||||||
],
|
"signDate": "input[name='fdSigndate']",
|
||||||
"customerCode": [
|
"beginDate": "input[name='fdEffedate']",
|
||||||
"label=客户编码",
|
"endDate": "input[name='fdEnddate']",
|
||||||
"input[name='fdCustomerCode']",
|
"isMajorContract": "input[name='extendDataFormInfo.value(fd_is_major_contract)']",
|
||||||
"input[placeholder*='客户编码']"
|
"isPrepayment": "input[name='extendDataFormInfo.value(fd_advance_amout)']",
|
||||||
],
|
"belongYear": "input[name='extendDataFormInfo.value(fd_contract_year_text)']",
|
||||||
"customerName": [
|
"paymentMethod": "input[name='extendDataFormInfo.value(fd_paytype)']",
|
||||||
"label=客户名称",
|
"paymentDays": "input[name='extendDataFormInfo.value(fd_pay_time_limit)']",
|
||||||
"input[name='fdCustomerName']",
|
"contractStatus": "input[name='extendDataFormInfo.value(fd_suconstatus)']",
|
||||||
"input[placeholder*='客户名称']"
|
"containsBloodProducts": "input[name='extendDataFormInfo.value(fd_3d1ce3953f1162)']",
|
||||||
],
|
"goodsOperationCode": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_opcode)']",
|
||||||
"contractVersionType": [
|
"goodsCode": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_opcode)']",
|
||||||
"label=合同版本类型",
|
"goodsName": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodsname)']",
|
||||||
"select[name='fdTemplateType']"
|
"goodsMdmCode": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_mdmCode)']",
|
||||||
],
|
"goodsApprovalNo": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_registdocno)']",
|
||||||
"contractCategory": [
|
"goodsSpec": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodstype)']",
|
||||||
"label=合同类别",
|
"goodsUnit": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)']",
|
||||||
"select[name='fdContractCategory']"
|
"goodsQuantity": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)']",
|
||||||
],
|
"goodsUnitPrice": "input[name='extendDataFormInfo.value(fd_detail1.0.fd_unitprice)']",
|
||||||
"settlementType": [
|
"submit": "text=提交"
|
||||||
"label=结算类型",
|
|
||||||
"select[name='fdSettlementType']"
|
|
||||||
],
|
|
||||||
"contractBackground": [
|
|
||||||
"label=合同背景概述",
|
|
||||||
"textarea[name='fdContractBackground']"
|
|
||||||
],
|
|
||||||
"contractNatureMain": [
|
|
||||||
"label=主合同",
|
|
||||||
"text=主合同"
|
|
||||||
],
|
|
||||||
"amountCurrency": [
|
|
||||||
"label=合同金额",
|
|
||||||
"select[name='fdCurrency']"
|
|
||||||
],
|
|
||||||
"amount": [
|
|
||||||
"label=金额(元)",
|
|
||||||
"input[name='fdAmount']"
|
|
||||||
],
|
|
||||||
"signDate": [
|
|
||||||
"label=合同签订日期",
|
|
||||||
"input[name='fdSignDate']",
|
|
||||||
"input[placeholder*='合同签订']"
|
|
||||||
],
|
|
||||||
"beginDate": [
|
|
||||||
"label=合同开始日期",
|
|
||||||
"input[name='fdStartDate']",
|
|
||||||
"input[placeholder*='合同开始']"
|
|
||||||
],
|
|
||||||
"endDate": [
|
|
||||||
"label=合同结束日期",
|
|
||||||
"input[name='fdEndDate']",
|
|
||||||
"input[placeholder*='合同结束']"
|
|
||||||
],
|
|
||||||
"isMajorContract": [
|
|
||||||
"label=是否重大合同",
|
|
||||||
"select[name='fdIsMajorContract']"
|
|
||||||
],
|
|
||||||
"isPrepayment": [
|
|
||||||
"label=是否预付款",
|
|
||||||
"select[name='fdIsPrepayment']"
|
|
||||||
],
|
|
||||||
"belongYear": [
|
|
||||||
"label=合同归属年份",
|
|
||||||
"input[name='fdBelongYear']"
|
|
||||||
],
|
|
||||||
"paymentMethod": [
|
|
||||||
"label=付款方式",
|
|
||||||
"select[name='fdPaymentMethod']",
|
|
||||||
"select[name='paymentMethod']"
|
|
||||||
],
|
|
||||||
"paymentDays": [
|
|
||||||
"label=付款期限",
|
|
||||||
"input[name='fdPaymentDays']",
|
|
||||||
"input[name='paymentDays']"
|
|
||||||
],
|
|
||||||
"contractStatus": [
|
|
||||||
"label=合同状态",
|
|
||||||
"select[name='fdContractStatus']"
|
|
||||||
],
|
|
||||||
"containsBloodProducts": [
|
|
||||||
"label=是否包含血液制品",
|
|
||||||
"select[name='fdContainsBloodProducts']"
|
|
||||||
],
|
|
||||||
"submit": [
|
|
||||||
"text=提交",
|
|
||||||
"text=提交送审",
|
|
||||||
"text=送审"
|
|
||||||
],
|
|
||||||
"contractNumber": [
|
|
||||||
"input[name='contractNo']",
|
|
||||||
"input[name='contractSystemNo']",
|
|
||||||
"label=合同编号",
|
|
||||||
"text=合同编号"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,425 +0,0 @@
|
||||||
"""Verification script for CONTRACT_CREATE flow logic.
|
|
||||||
|
|
||||||
Runs all core code paths WITHOUT browser automation or state center HTTP calls.
|
|
||||||
Uses mock objects to verify contract_flow_steps logic is correct.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Ensure the parent codes directory is on sys.path so rpa_yisaiqi imports work
|
|
||||||
_CODES = Path(__file__).resolve().parent.parent
|
|
||||||
if str(_CODES) not in sys.path:
|
|
||||||
sys.path.insert(0, str(_CODES))
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Phase 1: Verify all imports resolve correctly
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("Phase 1: 验证模块导入")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from rpa_yisaiqi.callback_client import StateCenterClient, StateCenterError
|
|
||||||
print(" [OK] callback_client -> StateCenterClient")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] callback_client: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from rpa_yisaiqi.contract_flow_steps import (
|
|
||||||
BrowserAdapter, ContractFlowContext, ContractGoodsLine,
|
|
||||||
contract_version_label, run_contract_create_flow,
|
|
||||||
open_contract_system, login_contract_system, enter_contract_create,
|
|
||||||
select_contract_template, fill_contract_signatory_info,
|
|
||||||
fill_contract_basic_info, fill_contract_amount_and_dates,
|
|
||||||
fill_contract_goods_lines, fill_contract_payment_info,
|
|
||||||
upload_contract_document_if_needed, submit_contract_for_approval,
|
|
||||||
read_contract_number,
|
|
||||||
)
|
|
||||||
print(" [OK] contract_flow_steps -> all functions")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] contract_flow_steps: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from rpa_yisaiqi.contract_draft import ContractDraft, load_contract_draft, contract_draft_from_dict
|
|
||||||
print(" [OK] contract_draft -> ContractDraft, load_contract_draft")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] contract_draft: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from rpa_yisaiqi.pdf_utils import ensure_merged_pdf
|
|
||||||
print(" [OK] pdf_utils -> ensure_merged_pdf")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] pdf_utils: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from rpa_yisaiqi.contract_create_node import (
|
|
||||||
ContractCreateInput, ContractCreateError,
|
|
||||||
LoginFailed, SupplierContractDisabled, PdfMergeUnsupported,
|
|
||||||
execute_contract_create, create_contract_in_contract_system,
|
|
||||||
build_complete_payload, build_fail_payload, build_flow_context,
|
|
||||||
NODE_CODE, CONTRACT_URL, parse_args,
|
|
||||||
)
|
|
||||||
print(" [OK] contract_create_node -> all functions + error classes")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] contract_create_node: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(" -> 6/6 模块导入成功\n")
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Phase 2: Verify contract draft loading from JSON examples
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("Phase 2: 验证合同起草 JSON 数据加载")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
examples_dir = Path(__file__).resolve().parent / "examples"
|
|
||||||
|
|
||||||
# Test YYC draft
|
|
||||||
yyc_path = examples_dir / "contract_draft_yyc.json"
|
|
||||||
if yyc_path.exists():
|
|
||||||
try:
|
|
||||||
draft = load_contract_draft(str(yyc_path))
|
|
||||||
assert draft.supplier_name == "重庆亿昊药业", f"supplier_name mismatch: {draft.supplier_name}"
|
|
||||||
assert draft.source_type == "YYC", f"source_type mismatch: {draft.source_type}"
|
|
||||||
assert draft.template_type == "NON_FORMAT", f"template_type mismatch: {draft.template_type}"
|
|
||||||
assert draft.settlement_type == "付款"
|
|
||||||
assert draft.contract_nature == "主合同"
|
|
||||||
assert len(draft.goods_lines) == 1, f"goods_lines count mismatch: {len(draft.goods_lines)}"
|
|
||||||
assert draft.goods_lines[0].goods_name == "阿卡波糖片"
|
|
||||||
assert len(draft.platform_pdf_paths) == 2
|
|
||||||
print(f" [OK] contract_draft_yyc.json -> supplier={draft.supplier_name}, "
|
|
||||||
f"goods={len(draft.goods_lines)}, pdfs={len(draft.platform_pdf_paths)}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] contract_draft_yyc.json: {e}")
|
|
||||||
import traceback; traceback.print_exc()
|
|
||||||
else:
|
|
||||||
print(f" [SKIP] contract_draft_yyc.json not found")
|
|
||||||
|
|
||||||
# Test XYY draft
|
|
||||||
xyy_path = examples_dir / "contract_draft_xyy.json"
|
|
||||||
if xyy_path.exists():
|
|
||||||
try:
|
|
||||||
draft = load_contract_draft(str(xyy_path))
|
|
||||||
print(f" [OK] contract_draft_xyy.json -> supplier={draft.supplier_name}, "
|
|
||||||
f"source_type={draft.source_type}, goods={len(draft.goods_lines)}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [FAIL] contract_draft_xyy.json: {e}")
|
|
||||||
else:
|
|
||||||
print(f" [SKIP] contract_draft_xyy.json not found (may be fine)")
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Phase 3: Mock Browser and verify flow steps
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("Phase 3: 用 Mock Browser 验证流程步骤逻辑")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
|
|
||||||
class MockBrowser:
|
|
||||||
"""Records all interactions for verification."""
|
|
||||||
def __init__(self):
|
|
||||||
self.log: list[str] = []
|
|
||||||
self.values: dict[str, str] = {}
|
|
||||||
|
|
||||||
def open_url(self, url: str) -> None:
|
|
||||||
self.log.append(f"open_url({url})")
|
|
||||||
|
|
||||||
def fill_first(self, selectors: list[str], value: str) -> None:
|
|
||||||
self.log.append(f"fill_first({selectors[0] if selectors else '?'}..., {value})")
|
|
||||||
|
|
||||||
def click_first(self, selectors: list[str]) -> None:
|
|
||||||
self.log.append(f"click_first({selectors[0] if selectors else '?'}...)")
|
|
||||||
|
|
||||||
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None:
|
|
||||||
self.log.append(f"wait_for_text({text}, timeout={timeout_seconds})")
|
|
||||||
|
|
||||||
def select_first(self, selectors: list[str], value: str) -> None:
|
|
||||||
self.log.append(f"select_first({selectors[0] if selectors else '?'}..., {value})")
|
|
||||||
|
|
||||||
def upload_file_first(self, selectors: list[str], file_path: str) -> None:
|
|
||||||
self.log.append(f"upload_file_first({selectors[0] if selectors else '?'}..., {file_path})")
|
|
||||||
|
|
||||||
def read_first(self, selectors: list[str], value_name: str) -> str:
|
|
||||||
val = self.values.get(value_name, f"HT-20260812-{len(self.log):03d}")
|
|
||||||
self.log.append(f"read_first({selectors[0] if selectors else '?'}..., {value_name}) -> {val}")
|
|
||||||
return val
|
|
||||||
|
|
||||||
def screenshot(self, name: str) -> str:
|
|
||||||
path = f"screenshots/{name}.png"
|
|
||||||
self.log.append(f"screenshot({name}) -> {path}")
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
# Build flow context for YYC
|
|
||||||
ctx = ContractFlowContext(
|
|
||||||
contract_url="http://contract-test.gzmpc.com:8080/",
|
|
||||||
username="test002",
|
|
||||||
password="1",
|
|
||||||
supplier_name="测试供应商",
|
|
||||||
order_date="2026-08-12",
|
|
||||||
source_type="YYC",
|
|
||||||
template_type="NON_FORMAT",
|
|
||||||
template_name="非格式--采购协议",
|
|
||||||
customer_code="CUST-001",
|
|
||||||
customer_name="测试供应商",
|
|
||||||
contract_category="采购协议",
|
|
||||||
settlement_type="付款",
|
|
||||||
contract_background="自动化测试合同起草",
|
|
||||||
contract_nature="主合同",
|
|
||||||
amount_currency="人民币",
|
|
||||||
amount="50000.00",
|
|
||||||
contract_end_date="2026-12-31",
|
|
||||||
is_major_contract="否",
|
|
||||||
is_prepayment="否",
|
|
||||||
belong_year="2026",
|
|
||||||
payment_method="账期支付",
|
|
||||||
payment_days="30",
|
|
||||||
contract_status="正式合同",
|
|
||||||
contains_blood_products="否",
|
|
||||||
submit_mode="dry_run", # dry-run mode
|
|
||||||
goods_lines=[
|
|
||||||
ContractGoodsLine(
|
|
||||||
operation_code="ADD",
|
|
||||||
goods_code="YP-001",
|
|
||||||
goods_name="测试药品A",
|
|
||||||
approval_no="国药准字H00000001",
|
|
||||||
spec="50mg*30片",
|
|
||||||
unit="盒",
|
|
||||||
quantity=10.0,
|
|
||||||
),
|
|
||||||
ContractGoodsLine(
|
|
||||||
operation_code="ADD",
|
|
||||||
goods_code="YP-002",
|
|
||||||
goods_name="测试药品B",
|
|
||||||
approval_no="国药准字H00000002",
|
|
||||||
spec="100mg*20片",
|
|
||||||
unit="瓶",
|
|
||||||
quantity=5.0,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
merged_pdf_path="C:/rpa/jobs/test/merged.pdf",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run the flow
|
|
||||||
browser = MockBrowser()
|
|
||||||
contract_no = run_contract_create_flow(browser, ctx)
|
|
||||||
|
|
||||||
# Verify step sequence
|
|
||||||
expected_steps = [
|
|
||||||
"open_url",
|
|
||||||
"fill_first", # username
|
|
||||||
"fill_first", # password
|
|
||||||
"click_first", # login
|
|
||||||
"wait_for_text", # 合同
|
|
||||||
"click_first", # 合同管理
|
|
||||||
"click_first", # 合同起草
|
|
||||||
"wait_for_text", # 最近使用模板
|
|
||||||
"click_first", # 模板
|
|
||||||
"wait_for_text", # 合同起草
|
|
||||||
]
|
|
||||||
|
|
||||||
step_names = [s.split("(")[0] for s in browser.log]
|
|
||||||
|
|
||||||
assert contract_no == "", f"dry_run should return empty string, got: {contract_no}"
|
|
||||||
print(f" [OK] dry-run mode: contract_no is empty string (not submitted)")
|
|
||||||
|
|
||||||
# Verify key steps were executed
|
|
||||||
assert "open_url" in step_names, "open_url not called"
|
|
||||||
print(f" [OK] open_url called with: {browser.log[0]}")
|
|
||||||
|
|
||||||
assert len([s for s in step_names if s == "fill_first"]) >= 2, "fill_first not called enough times"
|
|
||||||
print(f" [OK] fill_first called for username/password and form fields")
|
|
||||||
|
|
||||||
assert "click_first" in step_names, "click_first not called"
|
|
||||||
print(f" [OK] click_first operations executed")
|
|
||||||
|
|
||||||
assert "screenshot" in step_names, "screenshot not called"
|
|
||||||
print(f" [OK] screenshots captured at each stage")
|
|
||||||
|
|
||||||
# Verify dry-run submitted correctly (returns False)
|
|
||||||
assert len([s for s in browser.log if "dry-run" in s]) == 1
|
|
||||||
print(f" [OK] dry-run screenshot taken, submit skipped")
|
|
||||||
|
|
||||||
print(f"\n Total browser actions: {len(browser.log)}")
|
|
||||||
for i, action in enumerate(browser.log):
|
|
||||||
print(f" [{i+1}] {action}")
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Phase 4: Verify XYY flow (skip PDF upload)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("Phase 4: 验证 XYY 流程 (跳过 PDF 上传)")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
ctx_xyy = ContractFlowContext(
|
|
||||||
contract_url="http://contract-test.gzmpc.com:8080/",
|
|
||||||
username="test002",
|
|
||||||
password="1",
|
|
||||||
supplier_name="小药药供应商",
|
|
||||||
order_date="2026-08-12",
|
|
||||||
source_type="XYY",
|
|
||||||
template_type="FORMAT",
|
|
||||||
template_name="购销协议书(2025)",
|
|
||||||
customer_code="CUST-XYY-001",
|
|
||||||
customer_name="小药药供应商",
|
|
||||||
contract_category="购销协议",
|
|
||||||
settlement_type="付款",
|
|
||||||
contract_background="XYY格式合同测试",
|
|
||||||
contract_nature="主合同",
|
|
||||||
amount_currency="人民币",
|
|
||||||
amount="30000.00",
|
|
||||||
contract_end_date="2026-12-31",
|
|
||||||
is_major_contract="否",
|
|
||||||
is_prepayment="否",
|
|
||||||
belong_year="2026",
|
|
||||||
payment_method="账期支付",
|
|
||||||
payment_days="30",
|
|
||||||
contract_status="正式合同",
|
|
||||||
contains_blood_products="否",
|
|
||||||
submit_mode="submit",
|
|
||||||
goods_lines=[],
|
|
||||||
merged_pdf_path=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
browser_xyy = MockBrowser()
|
|
||||||
browser_xyy.values["contract_number"] = "HT-20260812-XYY-001"
|
|
||||||
|
|
||||||
contract_no_xyy = run_contract_create_flow(browser_xyy, ctx_xyy)
|
|
||||||
|
|
||||||
# Verify XYY specific behavior
|
|
||||||
upload_calls = [s for s in browser_xyy.log if "upload" in s.lower()]
|
|
||||||
assert len(upload_calls) == 0, f"XYY should NOT upload PDF, but got: {upload_calls}"
|
|
||||||
print(f" [OK] XYY correctly skips PDF upload (0 upload calls)")
|
|
||||||
|
|
||||||
assert contract_no_xyy == "HT-20260812-XYY-001"
|
|
||||||
print(f" [OK] XYY contract number read: {contract_no_xyy}")
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Phase 5: Verify error classes and payload builders
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("Phase 5: 验证异常类和回调 Payload 构建")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
spec = ContractCreateInput(
|
|
||||||
job_id="TEST-JOB-001",
|
|
||||||
supplier_name="测试供应商",
|
|
||||||
order_date="2026-08-12",
|
|
||||||
source_type="YYC",
|
|
||||||
contract_system_no=None,
|
|
||||||
attempt_no=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test build_complete_payload
|
|
||||||
complete_payload = build_complete_payload(spec, "HT-20260812-001")
|
|
||||||
assert complete_payload["businessRefs"]["contractSystemNo"] == "HT-20260812-001"
|
|
||||||
assert "attemptNo" in complete_payload
|
|
||||||
assert "idempotencyKey" in complete_payload
|
|
||||||
print(f" [OK] build_complete_payload -> {complete_payload['businessRefs']}")
|
|
||||||
|
|
||||||
# Test error classes
|
|
||||||
for exc_class, code, retryable, role in [
|
|
||||||
(LoginFailed, "SYS_LOGIN_FAIL", True, "OPS"),
|
|
||||||
(SupplierContractDisabled, "BIZ_CONTRACT_DISABLED", False, "PURCHASER"),
|
|
||||||
(PdfMergeUnsupported, "CAP_PDF_MERGE_UNSUPPORTED", False, "OPS"),
|
|
||||||
]:
|
|
||||||
exc = exc_class("测试异常")
|
|
||||||
assert exc.error_code == code, f"{exc_class.__name__}: expected {code}, got {exc.error_code}"
|
|
||||||
assert exc.retryable == retryable
|
|
||||||
assert exc.route_role == role
|
|
||||||
print(f" [OK] {exc_class.__name__}: code={exc.error_code}, retryable={exc.retryable}, role={exc.route_role}")
|
|
||||||
|
|
||||||
# Test build_fail_payload
|
|
||||||
fail_payload = build_fail_payload(spec, LoginFailed("登录凭证无效"))
|
|
||||||
assert fail_payload["errorCode"] == "SYS_LOGIN_FAIL"
|
|
||||||
assert fail_payload["retryable"] is True
|
|
||||||
assert fail_payload["routeRole"] == "OPS"
|
|
||||||
print(f" [OK] build_fail_payload -> errorCode={fail_payload['errorCode']}")
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Phase 6: Verify contract_version_label
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("Phase 6: 验证工具函数")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
from rpa_yisaiqi.contract_flow_steps import contract_version_label
|
|
||||||
|
|
||||||
ctx_format = ContractFlowContext(
|
|
||||||
contract_url="", username="", password="", supplier_name="", order_date="",
|
|
||||||
source_type="", template_type="FORMAT",
|
|
||||||
)
|
|
||||||
assert contract_version_label(ctx_format) == "格式合同"
|
|
||||||
print(f" [OK] contract_version_label(FORMAT) -> '格式合同'")
|
|
||||||
|
|
||||||
ctx_nonformat = ContractFlowContext(
|
|
||||||
contract_url="", username="", password="", supplier_name="", order_date="",
|
|
||||||
source_type="", template_type="NON_FORMAT",
|
|
||||||
)
|
|
||||||
assert contract_version_label(ctx_nonformat) == "非格式合同"
|
|
||||||
print(f" [OK] contract_version_label(NON_FORMAT) -> '非格式合同'")
|
|
||||||
|
|
||||||
# Verify template default name logic
|
|
||||||
# (tested inside select_contract_template via the mock - NON_FORMAT without name uses '非格式--采购协议')
|
|
||||||
ctx_no_name = ContractFlowContext(
|
|
||||||
contract_url="", username="", password="", supplier_name="", order_date="",
|
|
||||||
source_type="", template_type="NON_FORMAT", template_name="",
|
|
||||||
)
|
|
||||||
browser_tpl = MockBrowser()
|
|
||||||
select_contract_template(browser_tpl, ctx_no_name)
|
|
||||||
assert "非格式--采购协议" in browser_tpl.log[0]
|
|
||||||
print(f" [OK] select_contract_template without name defaults to '非格式--采购协议'")
|
|
||||||
|
|
||||||
ctx_formatted = ContractFlowContext(
|
|
||||||
contract_url="", username="", password="", supplier_name="", order_date="",
|
|
||||||
source_type="", template_type="FORMAT", template_name="",
|
|
||||||
)
|
|
||||||
browser_tpl2 = MockBrowser()
|
|
||||||
select_contract_template(browser_tpl2, ctx_formatted)
|
|
||||||
assert "购销协议书(2025)" in browser_tpl2.log[0]
|
|
||||||
print(f" [OK] select_contract_template FORMAT defaults to '购销协议书(2025)'")
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Final Summary
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
print("=" * 60)
|
|
||||||
print("验证完成!")
|
|
||||||
print("=" * 60)
|
|
||||||
print(f"""
|
|
||||||
核心模块状态:
|
|
||||||
- callback_client.py : 导入正常 (StateCenterClient)
|
|
||||||
- contract_flow_steps.py : 所有15个步骤函数可调用
|
|
||||||
- contract_draft.py : JSON数据加载正常
|
|
||||||
- pdf_utils.py : 导入正常
|
|
||||||
- contract_create_node.py: 异常类定义正常, payload构建正确
|
|
||||||
|
|
||||||
流程逻辑验证:
|
|
||||||
- YYC dry-run 流程 : 所有步骤按序执行 (不含提交)
|
|
||||||
- XYY 流程 : PDF上传步骤被正确跳过
|
|
||||||
- 异常映射 : 4种异常类 = 4种错误码
|
|
||||||
- 版本类型 : 格式/非格式合同标签正确
|
|
||||||
|
|
||||||
下一步: 在 APA Studio 中:
|
|
||||||
1. 打开项目 D:/RPA/AutoRpa
|
|
||||||
2. 在画布上拖拽 'Python脚本块' 节点
|
|
||||||
3. 粘贴 GlobalFun.py 中的 run_contract_create_flow 调用
|
|
||||||
""")
|
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""用真实 Selenium Chrome 验证合同建单填表流程(dry_run,不提交)。"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 确保能 import rpa_yisaiqi 包
|
||||||
|
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PARENT = os.path.dirname(BASE)
|
||||||
|
if PARENT not in sys.path:
|
||||||
|
sys.path.insert(0, PARENT)
|
||||||
|
|
||||||
|
from rpa_yisaiqi.apa_browser_adapter import ApaBrowserAdapter
|
||||||
|
from rpa_yisaiqi.contract_flow_steps import (
|
||||||
|
ContractFlowContext,
|
||||||
|
ContractGoodsLine,
|
||||||
|
login_contract_system,
|
||||||
|
enter_contract_create,
|
||||||
|
select_contract_template,
|
||||||
|
fill_contract_signatory_info,
|
||||||
|
fill_contract_basic_info,
|
||||||
|
fill_contract_amount_and_dates,
|
||||||
|
fill_contract_goods_lines,
|
||||||
|
fill_contract_payment_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONTRACT_URL = "http://contract-test.gzmpc.com:8080/"
|
||||||
|
|
||||||
|
|
||||||
|
def build_ctx() -> ContractFlowContext:
|
||||||
|
return ContractFlowContext(
|
||||||
|
contract_url=CONTRACT_URL,
|
||||||
|
username="test002",
|
||||||
|
password="1",
|
||||||
|
supplier_name="测试供应商",
|
||||||
|
order_date="2026-08-12",
|
||||||
|
source_type="YYC",
|
||||||
|
template_type="NON_FORMAT",
|
||||||
|
template_name="非格式-采购协议",
|
||||||
|
customer_code="CUST-001",
|
||||||
|
customer_name="测试供应商",
|
||||||
|
contract_category="采购协议",
|
||||||
|
settlement_type="付款",
|
||||||
|
contract_background="自动化测试合同起草",
|
||||||
|
contract_nature="主合同",
|
||||||
|
amount="50000",
|
||||||
|
contract_end_date="2026-12-31",
|
||||||
|
is_major_contract="否",
|
||||||
|
is_prepayment="否",
|
||||||
|
belong_year="2026",
|
||||||
|
payment_method="账期支付",
|
||||||
|
payment_days="30",
|
||||||
|
contract_status="正式合同",
|
||||||
|
contains_blood_products="否",
|
||||||
|
submit_mode="dry_run",
|
||||||
|
goods_lines=[
|
||||||
|
ContractGoodsLine(
|
||||||
|
operation_code="ADD",
|
||||||
|
goods_code="YP-001",
|
||||||
|
goods_name="测试药品A",
|
||||||
|
approval_no="国药准字H00000001",
|
||||||
|
spec="50mg*30片",
|
||||||
|
unit="盒",
|
||||||
|
quantity=10,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def read_values(driver):
|
||||||
|
"""回读表单中已填入的值,用于校验。"""
|
||||||
|
script = """
|
||||||
|
function getVal(name) {
|
||||||
|
var el = document.querySelector('input[name="' + name + '"],textarea[name="' + name + '"],select[name="' + name + '"]');
|
||||||
|
return el ? (el.value || '') : '(missing)';
|
||||||
|
}
|
||||||
|
var keys = [
|
||||||
|
'extendDataFormInfo.value(fd_customeropcode)',
|
||||||
|
'extendDataFormInfo.value(fd_customername)',
|
||||||
|
'extendDataFormInfo.value(fd_contract_cate_type)',
|
||||||
|
'fdAccount',
|
||||||
|
'fdSigndate',
|
||||||
|
'fdEffedate',
|
||||||
|
'fdEnddate',
|
||||||
|
'extendDataFormInfo.value(fd_is_major_contract)',
|
||||||
|
'extendDataFormInfo.value(fd_advance_amout)',
|
||||||
|
'extendDataFormInfo.value(fd_contract_year_text)',
|
||||||
|
'extendDataFormInfo.value(fd_paytype)',
|
||||||
|
'extendDataFormInfo.value(fd_pay_time_limit)',
|
||||||
|
'extendDataFormInfo.value(fd_suconstatus)',
|
||||||
|
'extendDataFormInfo.value(fd_3d1ce3953f1162)',
|
||||||
|
'extendDataFormInfo.value(fd_detail1.0.fd_opcode)',
|
||||||
|
'extendDataFormInfo.value(fd_detail1.0.fd_goodsname)',
|
||||||
|
'extendDataFormInfo.value(fd_detail1.0.fd_registdocno)',
|
||||||
|
'extendDataFormInfo.value(fd_detail1.0.fd_goodstype)',
|
||||||
|
'extendDataFormInfo.value(fd_detail1.0.fd_goodsunit)',
|
||||||
|
'extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)'
|
||||||
|
];
|
||||||
|
var out = {};
|
||||||
|
for (var i = 0; i < keys.length; i++) { out[keys[i]] = getVal(keys[i]); }
|
||||||
|
var bg = document.querySelector('textarea[name="extendDataFormInfo.value(fd_desc_info)"]');
|
||||||
|
out['background'] = bg ? bg.value : '(missing)';
|
||||||
|
return JSON.stringify(out);
|
||||||
|
"""
|
||||||
|
return driver.execute_script(script)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 60)
|
||||||
|
print("Selenium 合同建单填表验证 (dry_run, 不提交)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
adapter = ApaBrowserAdapter(CONTRACT_URL)
|
||||||
|
ctx = build_ctx()
|
||||||
|
|
||||||
|
print("[1/6] 打开合同系统并登录 ...")
|
||||||
|
adapter.open_url(CONTRACT_URL)
|
||||||
|
time.sleep(2)
|
||||||
|
login_contract_system(adapter, ctx)
|
||||||
|
print(" [OK] 登录成功")
|
||||||
|
|
||||||
|
print("[2/6] 进入合同起草页 ...")
|
||||||
|
enter_contract_create(adapter)
|
||||||
|
print(" [OK] 已进入模板选择页")
|
||||||
|
|
||||||
|
print("[3/6] 选择模板 ...")
|
||||||
|
select_contract_template(adapter, ctx)
|
||||||
|
time.sleep(2)
|
||||||
|
print(" [OK] 模板已选择")
|
||||||
|
|
||||||
|
print("[4/6] 填写签约方 + 基础信息 ...")
|
||||||
|
fill_contract_signatory_info(adapter, ctx)
|
||||||
|
fill_contract_basic_info(adapter, ctx)
|
||||||
|
print(" [OK] 已填写")
|
||||||
|
|
||||||
|
print("[5/6] 填写金额日期 + 货品 + 付款 ...")
|
||||||
|
fill_contract_amount_and_dates(adapter, ctx)
|
||||||
|
fill_contract_goods_lines(adapter, ctx)
|
||||||
|
fill_contract_payment_info(adapter, ctx)
|
||||||
|
print(" [OK] 已填写")
|
||||||
|
|
||||||
|
print("[6/6] 回读校验 ...")
|
||||||
|
time.sleep(1)
|
||||||
|
values = read_values(adapter._driver)
|
||||||
|
print(" 回读值:")
|
||||||
|
import json
|
||||||
|
for k, v in json.loads(values).items():
|
||||||
|
print(f" {k} = {v}")
|
||||||
|
|
||||||
|
# 校验关键字段
|
||||||
|
expected = {
|
||||||
|
"extendDataFormInfo.value(fd_customeropcode)": "CUST-001",
|
||||||
|
"extendDataFormInfo.value(fd_customername)": "测试供应商",
|
||||||
|
"extendDataFormInfo.value(fd_contract_cate_type)": "采购协议",
|
||||||
|
"fdAccount": "50000",
|
||||||
|
"fdSigndate": "2026-08-12",
|
||||||
|
"fdEnddate": "2026-12-31",
|
||||||
|
"extendDataFormInfo.value(fd_paytype)": "账期支付",
|
||||||
|
"extendDataFormInfo.value(fd_pay_time_limit)": "30",
|
||||||
|
"extendDataFormInfo.value(fd_detail1.0.fd_goodsqty)": "10",
|
||||||
|
}
|
||||||
|
actual = json.loads(values)
|
||||||
|
all_ok = True
|
||||||
|
for k, v in expected.items():
|
||||||
|
got = actual.get(k, "")
|
||||||
|
status = "OK" if str(got) == str(v) else "MISMATCH"
|
||||||
|
if status != "OK":
|
||||||
|
all_ok = False
|
||||||
|
print(f" [{status}] {k}: 期望={v} 实际={got}")
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("验证结果:", "全部通过" if all_ok else "存在不匹配,请检查")
|
||||||
|
print("=" * 60)
|
||||||
|
print("浏览器保持打开(detach),可人工核对。")
|
||||||
|
sys.exit(0 if all_ok else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
43
show_flow.py
43
show_flow.py
|
|
@ -1,43 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""验证 Main.seq 并展示流程"""
|
|
||||||
import json
|
|
||||||
|
|
||||||
data = json.load(open(r'd:\RPA\AutoRpa\Main.seq', 'r', encoding='utf-8'))
|
|
||||||
nodes = data['NodeList']
|
|
||||||
lines = data['LineList']
|
|
||||||
|
|
||||||
print(f"节点总数: {len(nodes)}")
|
|
||||||
print(f"连线总数: {len(lines)}")
|
|
||||||
print()
|
|
||||||
print("=" * 70)
|
|
||||||
print(" 合同建单 (CONTRACT_CREATE) 流程图")
|
|
||||||
print("=" * 70)
|
|
||||||
print()
|
|
||||||
|
|
||||||
for i, n in enumerate(nodes):
|
|
||||||
ui = n['NodeUI']
|
|
||||||
subtype = ui['NodeSubType'].replace('EnumWidget_Begin', 'START').replace('EnumWidget_Code_Custom', 'CODE')
|
|
||||||
title = ui['NodeTitle']
|
|
||||||
print(f" [{subtype:6s}] {title}")
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("=" * 70)
|
|
||||||
print(" 连线关系 (按序执行)")
|
|
||||||
print("=" * 70)
|
|
||||||
for i, l in enumerate(lines):
|
|
||||||
fr = l['LineStart']['Pidx'].split('/')[0][-6:]
|
|
||||||
to = l['LineEnd']['Pidx'].split('/')[0][-6:]
|
|
||||||
fr_node = next((n for n in nodes if n['NodeUI']['NodeTag'].endswith(fr)), None)
|
|
||||||
to_node = next((n for n in nodes if n['NodeUI']['NodeTag'].endswith(to)), None)
|
|
||||||
fr_title = fr_node['NodeUI']['NodeTitle'] if fr_node else fr
|
|
||||||
to_title = to_node['NodeUI']['NodeTitle'] if to_node else to
|
|
||||||
print(f" [{fr_title}] --> [{to_title}]")
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("=" * 70)
|
|
||||||
print(" 运行说明")
|
|
||||||
print("=" * 70)
|
|
||||||
print(" 1. 在 APA Studio 中按 Ctrl+R 运行流程")
|
|
||||||
print(" 2. S01 节点会初始化所有模块和上下文")
|
|
||||||
print(" 3. S02-S15 节点依次执行合同建单各步骤")
|
|
||||||
print(" 4. 输出面板显示每步执行状态")
|
|
||||||
Loading…
Reference in New Issue