Compare commits

..

671 Commits

Author SHA1 Message Date
0ac2e8514a Linkage 2026-08-05 20:03:29 +02:00
3bb2811e76 Linkage report 2026-08-05 18:09:23 +02:00
2bcbf169cf GL bug 2026-08-04 22:24:59 +02:00
a007ed2895 Validation XML update 2026-08-04 16:26:58 +02:00
792733c93b Revert "bug view"
This reverts commit 321eae8d91.
2026-08-04 16:12:18 +02:00
321eae8d91 bug view 2026-08-04 15:50:34 +02:00
7d7bd33b97 IBAN 2026-08-04 15:36:56 +02:00
7fcc90d0a4 Show weight report sale invoice reference 2026-07-31 09:01:39 +02:00
d81a8750d3 Fix reporting context XML compatibility 2026-07-31 08:49:56 +02:00
94d0f2f828 Show controller and sale invoice info on shipments 2026-07-29 22:13:40 +02:00
d76a9a8dd6 Fix position exposure header layout 2026-07-29 21:52:52 +02:00
705a1706c3 Add position exposure report 2026-07-29 21:42:31 +02:00
275008c160 Use standard Long and Short header for detail report 2026-07-29 21:19:18 +02:00
1d7a058d68 Ignore invalid UI ids in Long and Short detail filters 2026-07-29 21:11:31 +02:00
baf1641e1e Simplify Long and Short detail header 2026-07-29 21:02:40 +02:00
058bbc7612 Improve Long and Short detail header layout 2026-07-29 20:48:43 +02:00
de1156e5bc Fix Long and Short detail context view XML 2026-07-29 18:41:43 +02:00
3235a1ba57 Add Long and Short detail report 2026-07-29 18:08:22 +02:00
27d2a81b31 Group physical position reports 2026-07-29 09:23:30 +02:00
e5b986efc5 Improve Long and Short filter header 2026-07-29 08:44:31 +02:00
7dcb68ab69 Refine Long and Short position granularity 2026-07-28 22:02:35 +02:00
03c294ad8c Long & Short 2026-07-28 20:50:46 +02:00
aaa1490389 Long & Short 2026-07-28 20:32:58 +02:00
AzureAD\SylvainDUVERNAY
3994154cad Risk Management - Physical Open Position 2026-07-28 16:56:45 +02:00
979bbec013 Melya report 2026-07-28 14:04:17 +02:00
d0bafebbbe Melya template 2026-07-28 12:02:49 +02:00
9b9daf5467 Melya template 2026-07-28 11:45:35 +02:00
4f9d46cd4a Show countries on Melya invoice ports 2026-07-28 11:31:09 +02:00
07ba685c0c Refresh custom duplicate payment terms on party change 2026-07-28 11:08:24 +02:00
89d35b5499 Reset custom duplicate party addresses during copy 2026-07-28 11:01:20 +02:00
dfbd8db2b8 Show product origin in trade main terms 2026-07-28 10:39:37 +02:00
c5633bdeb8 Set trade line fee tree column widths 2026-07-28 10:08:41 +02:00
a181e9cfaa Align insurance surveyor address block 2026-07-28 08:30:38 +02:00
1ba95fd636 Render insurance surveyor address as block 2026-07-28 08:20:48 +02:00
34dd8eaf54 Fix insurance surveyor address rendering 2026-07-28 07:58:35 +02:00
a1bb0e8a04 Show insurance surveyor address 2026-07-28 07:35:59 +02:00
c62cee33af Refine trade line form layout 2026-07-27 21:46:11 +02:00
f8edbed1eb Fix purchase sale line layout labels 2026-07-27 21:31:29 +02:00
5596fed28b Purchase & Sale 2026-07-27 20:44:31 +02:00
0ffd43149b Create mirror action 2026-07-27 20:24:44 +02:00
9429f7d2e3 Faircot action 2026-07-26 22:35:08 +02:00
33d742ffce Faircot instruction 2026-07-26 21:04:48 +02:00
2afa454a03 Faircot action 2026-07-26 19:59:09 +02:00
439e9002aa Purchase & Sale 2026-07-26 16:56:14 +02:00
ada4c445c8 Purchase & Sale 2026-07-26 16:29:49 +02:00
b1f97ade55 Purchase & Sale 2026-07-26 15:01:14 +02:00
df4570aff8 Purchase & Sale 2026-07-26 12:22:00 +02:00
e284f193fb Purchase & Sale 2026-07-26 11:28:19 +02:00
3f14cd6323 Purchase form 2026-07-25 23:51:37 +02:00
fa6198296b Purchase form 2026-07-25 23:31:39 +02:00
94e60fef6c Purchase form 2026-07-25 22:47:41 +02:00
b282fe9bc0 Purchase form 2026-07-25 19:26:10 +02:00
ffd673f9ef Purchase form 2026-07-25 18:42:54 +02:00
8088fd5254 Purchase form 2026-07-25 18:16:13 +02:00
42d02cc6f8 Purchase form 2026-07-25 17:30:39 +02:00
af344b6fc7 Purchase form 2026-07-25 10:56:26 +02:00
264ff534cf Purchase form 2026-07-25 10:39:50 +02:00
64390eaecb Purchase form 2026-07-25 09:52:25 +02:00
99763f10f2 View log 2026-07-25 09:13:12 +02:00
9fa91f1255 Purchase form 2026-07-25 08:25:24 +02:00
6b29088c31 Purchase form 2026-07-24 21:37:28 +02:00
5515bfa15b Purchase form 2026-07-24 21:00:38 +02:00
e767f4543e Purchase form 2026-07-24 17:58:27 +02:00
f05046b8c9 Purchase form 2026-07-24 17:36:02 +02:00
28a9a334c3 Purchase form 2026-07-24 17:06:53 +02:00
da93139bcc Purchase form 2026-07-24 16:41:59 +02:00
12d9b046d2 Purchase form 2026-07-24 16:22:34 +02:00
914fecf3a0 Purchase form 2026-07-24 15:52:00 +02:00
94bbed79f0 Purchase form 2026-07-24 15:18:49 +02:00
0f8e620b0d Purchase form 2026-07-24 13:04:46 +02:00
5a5a0699f2 Purchase form 2026-07-24 12:29:25 +02:00
8118d3190a Lots Management 2026-07-22 12:48:27 +02:00
493dd54606 Get amount 2026-07-21 23:35:39 +02:00
ab80f8d909 Linkage 2026-07-21 21:40:35 +02:00
2fe13c1e76 Mark as finished 2026-07-21 17:47:10 +02:00
a70c2c4853 Faircot cron 2026-07-20 09:30:31 +02:00
d6fa273d11 Faircot cron 2026-07-20 09:16:28 +02:00
816d1fcaec Faircot cron 2026-07-20 08:43:43 +02:00
8973f8a6d0 Faircot process 2026-07-19 22:52:21 +02:00
572f6ef902 Faircot process 2026-07-19 22:42:57 +02:00
bd72f8cef3 Faircot cron 2026-07-19 21:59:22 +02:00
a94f36e26f Faircot report 2026-07-19 21:16:19 +02:00
669320c976 Faircot cron 2026-07-19 21:02:50 +02:00
facd627f5e Faircot cron 2026-07-19 19:30:28 +02:00
b2bd777ec8 Faircot report 2026-07-19 18:34:03 +02:00
71751a96c0 Faircot report 2026-07-19 18:17:06 +02:00
c655427be5 Bug col_width 2026-07-19 17:32:54 +02:00
0272fc70bc Bug Pnl packing 2026-07-17 07:28:35 +02:00
39afd9dc32 Cupping Criteria 2026-07-16 22:14:27 +02:00
0e30f9ac30 Cupping bug 2026-07-16 21:45:46 +02:00
8be68db6db Coffee form 2026-07-16 21:22:34 +02:00
aba25d62dc Hedging setup 2026-07-16 20:37:17 +02:00
4c85f80412 Lot virtual packing update 2026-07-16 18:53:55 +02:00
a0bd6f0e28 Bug Pnl 2026-07-16 15:09:54 +02:00
cda8b4067b Bug Add physical lots 2026-07-16 12:22:51 +02:00
728e2c7d60 Pnl 2026-07-16 10:11:11 +02:00
4652a1a94a Price linked currency 2026-07-16 09:05:08 +02:00
b53d7a216d Coffee visible 2026-07-15 23:26:07 +02:00
0e004027df Coffee hide 2026-07-15 22:18:42 +02:00
df8439f3f5 Bug tax_rate 2026-07-15 21:19:47 +02:00
cb35de91ba Coffee lines 2026-07-15 17:03:07 +02:00
59171ec07b Linkage 2026-07-15 08:51:00 +02:00
9b0dd0e7c1 Linkage 2026-07-15 07:58:02 +02:00
cf0c0923a6 Linkage 2026-07-14 18:15:26 +02:00
bdf13f4fac Merge branch 'main' of https://gitea.open-squared.tech/admin/tradon 2026-07-14 16:51:18 +02:00
e184da898f Linkage 2026-07-14 16:51:10 +02:00
AzureAD\SylvainDUVERNAY
b3e8dc9c33 Merge branch 'main' of https://gitea.open-squared.tech/admin/tradon 2026-07-14 16:13:24 +02:00
AzureAD\SylvainDUVERNAY
fab46dde2f Import Prices (All Sources) 2026-07-14 16:13:21 +02:00
3dfbb25281 Certification 2026-07-14 15:30:04 +02:00
eb38977dca Bug lot.qt 2026-07-14 13:47:40 +02:00
ac3198ee70 Bug lot.qt 2026-07-14 13:29:44 +02:00
cf4bd94d03 Bug lot.qt 2026-07-14 12:02:07 +02:00
07ee8b4f99 Bug lot.qt 2026-07-14 11:39:39 +02:00
7fc134f7a6 Bug lot.qt 2026-07-14 11:26:18 +02:00
e4590e6177 Polish linkage report table spacing 2026-07-14 10:41:50 +02:00
5a644d430b Refine linkage report spacing 2026-07-14 09:54:29 +02:00
AzureAD\SylvainDUVERNAY
808e75daa6 New file import format (ARGUS +ICIS) 2026-07-13 21:35:01 +02:00
AzureAD\SylvainDUVERNAY
febe29c6b4 Merge branch 'main' of https://gitea.open-squared.tech/admin/tradon 2026-07-13 20:53:00 +02:00
AzureAD\SylvainDUVERNAY
b312ea9bd3 add folder temp_documents with Excel file mapping used for price import 2026-07-13 20:52:52 +02:00
3321830b5e Improve linkage report layout 2026-07-13 19:20:03 +02:00
f2fe626d72 Add mid price value field 2026-07-13 16:52:53 +02:00
ddd88cd840 Add SOF calculation overrides 2026-07-12 13:10:13 +02:00
47d0f025b3 Support multiple laytime start candidates 2026-07-12 11:52:28 +02:00
8719a63fa5 Adjust purchase PnL graph diagnostics 2026-07-10 14:49:53 +02:00
27b865184c Show purchase PnL graph values 2026-07-10 13:26:01 +02:00
c2a523430e Fix purchase PnL graph data source 2026-07-10 12:52:23 +02:00
8d386e6754 Pnl graph 2026-07-10 12:22:16 +02:00
62eed772fc Demurrage 2026-07-09 17:31:49 +02:00
b0bcaf293d Demurrage 2026-07-09 17:27:01 +02:00
9cf1bc3604 Demurrage 2026-07-09 17:13:41 +02:00
858b7258d9 Pnl graph 2026-07-08 14:38:36 +02:00
3122255bc7 Bug invoice rate ICT 2026-07-07 20:55:47 +02:00
5c3464da5c Linkage report 2026-07-07 18:47:15 +02:00
82143ba87d Linkage report 2026-07-07 18:42:34 +02:00
1b292e4594 Linkage report 2026-07-07 18:07:42 +02:00
0f4edc16ce Linkage template 2026-07-07 17:45:59 +02:00
7ac1e118ae Linkage template 2026-07-07 16:58:55 +02:00
94fd181c11 Linkage template 2026-07-07 16:37:53 +02:00
4ea701d669 Linkage report 2026-07-07 15:58:59 +02:00
b3b14039e0 Linkage 2026-07-07 15:05:04 +02:00
ae39ff2f7d Melya template 2026-07-07 08:40:18 +02:00
2ffd99b03a Melya template 2026-07-06 12:26:48 +02:00
7d5ac651ab Melya template 2026-07-06 11:56:14 +02:00
e8b3fe5bc4 Custom duplicate bug 2026-07-03 11:31:27 +02:00
ea85927a17 Pnl clean up 2026-07-01 15:39:46 +02:00
99d5d2a849 Price composition + Pnl open matched 2026-06-30 18:27:40 +02:00
c0ac8fac68 Coffee 2026-06-29 23:05:27 +02:00
2743a7bc14 Coffee 2026-06-29 22:50:38 +02:00
cd49703879 Coffee 2026-06-29 22:43:03 +02:00
cc4a48c38c Coffee 2026-06-29 22:31:21 +02:00
6fedb92b1a Coffee 2026-06-29 22:24:51 +02:00
cad7f7235e Coffee 2026-06-29 21:58:11 +02:00
b08d2f4dab Coffee 2026-06-29 21:36:04 +02:00
2a30b03cbe Coffee 2026-06-29 21:32:19 +02:00
eb244b1e3e Coffee 2026-06-29 21:26:09 +02:00
f2947c9f86 Coffee 2026-06-29 21:16:42 +02:00
f9ade37eab Coffee 2026-06-29 20:34:23 +02:00
5635856711 Show line action domain filters 2026-06-28 15:48:12 +02:00
edc20fa546 ITSA points 28.06 2026-06-28 15:26:28 +02:00
d4f9d02cb4 ITSA points 28.06 2026-06-28 14:18:06 +02:00
780f3dcbdb ITSA points 28.06 2026-06-28 14:11:45 +02:00
d5c6d5c4b5 ITSA points 28.06 2026-06-28 13:51:10 +02:00
104d5c616c Purchase & Sale Lines 2026-06-28 13:43:26 +02:00
f45cdb0807 ITSA points 28.06 2026-06-28 13:31:48 +02:00
b7b54c8b7b Invoice Melya template 2026-06-26 14:28:26 +02:00
c9dceb9c06 Lot invoicing + Fee padding 2026-06-23 16:06:17 +02:00
cf7aa7f5b5 Lot invoicing + Fee padding 2026-06-23 15:28:11 +02:00
2998d8d570 Lot invoicing + Fee padding 2026-06-23 15:08:49 +02:00
83b879d200 Lot invoicing + Fee padding 2026-06-23 14:31:52 +02:00
50b262dbd2 Lot invoicing + Fee padding 2026-06-23 13:54:14 +02:00
fbf3842e6c Lot invoicing + fee padding 2026-06-23 13:12:37 +02:00
ef8d914820 Lot invoicing + Fee 2026-06-23 12:18:13 +02:00
423d9476fc Padding logic in fee 2026-06-23 09:21:10 +02:00
d08510f084 Lot invocing + Fee 2026-06-22 17:53:53 +02:00
ed818c2287 Lot invoicing + Fee invoicing 2026-06-22 17:44:55 +02:00
703868001b Fee report 2026-06-22 17:16:27 +02:00
6af3de0ffb Add physical lot + Link 2026-06-22 16:14:05 +02:00
95dba725a8 Add physical lot + Link 2026-06-22 16:00:55 +02:00
a880535bc4 Add physical + Link 2026-06-22 15:33:43 +02:00
8f676c2396 Lots Management add user and date 2026-06-22 13:15:55 +02:00
dfbc006efb Pnl bug sh fee 2026-06-19 12:39:58 +02:00
4f331d5c8e DN/CN commission 2026-06-19 09:21:52 +02:00
227a6c14a8 Commssion DN/CN 2026-06-19 08:34:29 +02:00
2b0381c2ae Fee qt state 2026-06-18 21:11:46 +02:00
2a1512a436 Duplicate from LM 2026-06-18 13:51:05 +02:00
1c20181d06 Duplicate custom 2026-06-18 13:33:41 +02:00
6c2061e779 Duplicate custom 2026-06-18 13:20:20 +02:00
1f24646227 Duplicate custom 2026-06-18 13:15:41 +02:00
2d737b2628 Duplicate custom 2026-06-18 13:05:34 +02:00
903b42f730 Duplicate custom 2026-06-18 12:59:16 +02:00
f8d53a27fa Duplicate custom 2026-06-18 11:54:01 +02:00
4b5e49f264 Duplicate custom 2026-06-18 11:34:19 +02:00
3b3f9c0988 Duplicate purchase 2026-06-18 11:12:56 +02:00
248e410d88 Scenario Mtm 2026-06-18 10:56:03 +02:00
a754e15e32 Mtm bug 2026-06-17 12:25:21 +02:00
ee183e23c5 Fee report 2026-06-17 11:56:25 +02:00
8441bf98e7 Fee report 2026-06-17 11:25:33 +02:00
02e5c43f3d Commission CN/DN 2026-06-17 11:04:43 +02:00
6ba5361269 CN/DN 2026-06-17 10:52:29 +02:00
f0714d5670 CN/DN 2026-06-17 10:23:13 +02:00
b8bfcc4308 CN/DN 2026-06-17 09:59:16 +02:00
c268c3b460 CN/DN 2026-06-17 09:46:57 +02:00
04b3c1d924 DN/CN 2026-06-17 09:41:57 +02:00
d8012bd2da CN/DN 2026-06-17 09:32:20 +02:00
dd2acc2d09 CN/DN commission 2026-06-17 09:19:21 +02:00
a8102d513f Fee CN/DN 2026-06-17 00:08:52 +02:00
ee1cb3e7fb Fee CN/DN 2026-06-16 23:47:35 +02:00
d0dd8965cd ICT template 2026-06-16 20:38:57 +02:00
0833baa2ab Fee linked curr 2026-06-16 19:53:29 +02:00
8eba0cd066 Pricing linked 2026-06-16 15:35:12 +02:00
38af948374 ICT template 2026-06-16 14:58:13 +02:00
36ee92cbb2 Pricing cron 2026-06-16 13:01:10 +02:00
fdf855f7db Pricing adj 2026-06-16 11:26:23 +02:00
31f617ae1c Bug Party 2026-06-16 10:40:34 +02:00
07e48944b6 Melya PO 2026-06-15 20:04:37 +02:00
3a0edea8cd ICT template 2026-06-15 18:14:42 +02:00
9b7ccf1feb Broker DN/CN ICT 2026-06-15 15:06:46 +02:00
dd8e3e3056 Dashboard bug 2026-06-15 13:31:26 +02:00
d8a0dd43cf Template ICT 2026-06-15 12:53:28 +02:00
7b427813be Fee linked unit 2026-06-15 11:31:09 +02:00
d705d86e80 Pricing Before/After 2026-06-15 08:28:59 +02:00
667b66487d Pricing adj 2026-06-14 17:29:28 +02:00
9c92904b3e Pricing adj 2026-06-14 09:48:39 +02:00
8a60a8e67f Matching price 2026-06-13 17:37:32 +02:00
e6de614507 Duplicate + open form 2026-06-13 16:48:45 +02:00
a7c061f52d Duplicate 2026-06-13 15:41:45 +02:00
e4e2087463 Duplicate custom 2026-06-13 15:21:36 +02:00
c15b13cb91 ITSA template 2026-06-13 14:57:30 +02:00
e981f96cad ITSA proforma 2026-06-13 14:13:41 +02:00
aac7bad852 Template bug 2026-06-13 12:25:49 +02:00
1e4d9d6e17 Sale bug 2026-06-13 12:16:52 +02:00
a0363fa9c7 Sale bug 2026-06-13 12:10:14 +02:00
e19dcafcb7 ITSA proforma 2026-06-13 11:58:27 +02:00
6a5a267cfd Unlink open when Add physical lot 2026-06-13 11:37:48 +02:00
5c9f437945 Lots Management 2026-06-13 11:25:57 +02:00
ec90d19cc7 Delivery period changed into Shipment period 2026-06-13 11:04:13 +02:00
77b1ab333b Remove physical lot for Planned 2026-06-13 10:38:19 +02:00
1f3e32fbe8 Pricing cron 2026-06-13 10:00:45 +02:00
a435631289 Valuation currency 2026-06-12 20:46:09 +02:00
1a5e2e4033 Mtm new 2026-06-12 20:34:47 +02:00
7107625664 Valuation fee 2026-06-12 19:39:16 +02:00
dbfd6a0d89 Valuation 2026-06-12 19:09:08 +02:00
6ac19a1334 Pricing default 2026-06-11 14:31:35 +02:00
855dc3a4dd Pricing default 2026-06-11 14:03:02 +02:00
52459a267b Pricing manual 2026-06-11 10:49:05 +02:00
9d13cfd587 Mtm bug 2026-06-10 15:20:35 +02:00
de8310891b Bug Valuation 2026-06-10 14:13:48 +02:00
925fdfd32b Shipment Pnl 2026-06-10 11:37:33 +02:00
96ad6dd1c6 Pnl shipment 2026-06-10 10:57:34 +02:00
68bd105e59 Template Melya 2026-06-09 14:32:01 +02:00
90f64cf55f Template 2026-06-09 14:27:58 +02:00
f722c6fa1c Template 2026-06-09 14:20:32 +02:00
217344d3d5 Template 2026-06-09 14:13:05 +02:00
5cb4317742 Template Melya 2026-06-09 14:08:33 +02:00
cf3ad4bb31 Template 2026-06-09 14:04:36 +02:00
fcc5c3866a Confirmed 2026-06-09 14:00:22 +02:00
f59150d1a9 Melya template + Confirmed 2026-06-09 13:43:21 +02:00
67cb5491e2 Melya template 2026-06-09 13:30:24 +02:00
734c0c8a34 Melya template 2026-06-09 11:12:23 +02:00
fd04810f0c Confirmed button 2026-06-09 10:35:27 +02:00
c8e8e4f866 Stop workflow to Confirmed 2026-06-08 23:12:17 +02:00
3a6f426033 Sale design 2026-06-08 21:44:08 +02:00
5838d848c2 Purchase design 2026-06-08 21:02:57 +02:00
79c701b711 Purchase design 2026-06-08 20:54:58 +02:00
c36c6a411f Purchase design 2026-06-08 20:47:22 +02:00
745325eb20 Purchase design 2026-06-08 20:33:28 +02:00
dbd5e5b77f Purchase design 2026-06-08 20:18:52 +02:00
c2f3d3995f Purchase design 2026-06-08 19:52:14 +02:00
02a15c93de Purchase design 2026-06-08 18:53:44 +02:00
abbcdfd2ec Purchase design 2026-06-08 18:44:14 +02:00
dc067fb812 Purchase design 2026-06-08 18:27:06 +02:00
8691ba430f Purchase header design 2026-06-08 18:10:22 +02:00
e2e0e8cece Purchase form design 2026-06-08 17:55:51 +02:00
d182b0eceb LM header 2026-06-08 14:04:52 +02:00
74d1465470 Pricing form 2026-06-07 21:43:37 +02:00
1abfbbbc3d Pricing form 2026-06-07 21:33:13 +02:00
72501ab625 Pricing date 2026-06-07 21:25:23 +02:00
65e9f4d7d6 BL nb stock move 2026-06-07 21:13:31 +02:00
0a72076346 PL SL design 2026-06-07 18:10:47 +02:00
3977e138e2 PL design 2026-06-07 17:56:50 +02:00
45acff11bc PL SL design 2026-06-07 17:39:16 +02:00
1b15ab4c69 PL SL design 2026-06-07 13:37:41 +02:00
7735306f9c PL SL design 2026-06-07 13:27:19 +02:00
76b34ba280 PL SL design 2026-06-07 13:17:44 +02:00
eb82211da7 PL design 2026-06-07 12:54:15 +02:00
c2495bb6ea PL design 2026-06-07 11:54:52 +02:00
9e5cd39629 Purchase line design 2026-06-07 11:48:32 +02:00
05a90f64a5 Purchase line design 2026-06-07 11:32:27 +02:00
4d3c5e26e2 Tab order 2026-06-07 11:11:05 +02:00
6ac1904a0c Planned 2026-06-07 10:16:15 +02:00
8e82986bd5 Planned 2026-06-07 09:57:57 +02:00
6ffc159255 Planned 2026-06-07 09:47:55 +02:00
4ebfbe6ddd Planned 2026-06-07 09:37:32 +02:00
700adb87f9 Link to transport 2026-06-07 07:53:18 +02:00
126225ea6a Link to transport 2026-06-07 07:41:33 +02:00
1339f9b2f1 Link to transport 2026-06-07 07:30:42 +02:00
b3b7803313 Link to transport 2026-06-07 07:23:32 +02:00
24b4e00080 Default From To 2026-06-07 07:02:18 +02:00
846c2d6a93 Planned status 2026-06-07 06:22:15 +02:00
801e53f953 Shipment creation bug 2026-06-07 05:45:21 +02:00
06f9a62dbc LM header 2026-06-06 21:23:03 +02:00
eceeca997e LM header 2026-06-06 21:16:17 +02:00
f37d21c761 LM header 2026-06-06 21:12:03 +02:00
1b97899091 LM header 2026-06-06 21:07:54 +02:00
afee4243cc Lots management header 2026-06-06 21:01:42 +02:00
743c76c131 Valuation open matché 2026-06-06 20:41:39 +02:00
acf0d4b687 Plan to transport 2026-06-06 19:57:32 +02:00
d659f14e12 Plan to transport 2026-06-06 19:46:51 +02:00
b74389f9a6 Pnl cron 2026-06-06 11:15:42 +02:00
b3c33ed314 Valuation cron 2026-06-06 11:03:53 +02:00
73006db98a Pnl 2026-06-05 19:51:30 +02:00
3599570d5e Valuation 2026-06-05 19:38:18 +02:00
2ae512e074 Valuation 2026-06-05 19:29:19 +02:00
90932a290d Pnl badge 2026-06-05 19:07:52 +02:00
77922997eb Pnl 2026-06-05 15:25:33 +02:00
574c1a67f4 Mtm 2026-06-05 07:46:48 +02:00
1de1d15064 Pnl 2026-06-04 21:56:25 +02:00
25800aa8a4 Pnl 2026-06-04 21:46:02 +02:00
5a377fda13 pnl 2026-06-04 21:36:25 +02:00
3ab58e63f8 Operator 2026-06-04 19:39:36 +02:00
a114c14e89 Pnl shipment 2026-06-04 19:27:50 +02:00
a98415fe62 Purchase tree 2026-06-04 18:45:09 +02:00
c272630a8a Invoice Melya template 2026-06-04 14:21:40 +02:00
9cc0f0715b Qt type 2026-06-04 12:09:41 +02:00
6860ba9e60 BL date 2026-06-04 10:44:40 +02:00
e9bd6ff57f Demurrage 2026-06-04 09:41:08 +02:00
0efa60f3b5 Demurrage 2026-06-03 20:16:13 +02:00
d3a118db07 Demurrage 2026-06-03 18:35:01 +02:00
48d0b6a3f6 Demurrage 2026-06-03 17:43:16 +02:00
4e1d6895f8 Demurrage 2026-06-03 17:00:51 +02:00
4a0461df44 demurrage 2026-06-03 16:29:50 +02:00
adcce718fd Demurrage 2026-06-03 14:30:16 +02:00
5763ca64a5 Demurrage 2026-06-03 14:14:51 +02:00
0094bd6974 Demurrage 2026-06-03 13:50:28 +02:00
c096b39259 Demurrage 2026-06-03 11:18:27 +02:00
654a8418bf Demurrage 2026-06-03 11:08:32 +02:00
dd4b8055a7 Merge branch 'main' of https://gitea.open-squared.tech/admin/tradon 2026-06-03 10:13:46 +02:00
9556f870ec Demurrage 2026-06-03 10:13:43 +02:00
AzureAD\SylvainDUVERNAY
4c08b95b3b Import Price Bug Fix 2026-06-03 10:05:36 +02:00
AzureAD\SylvainDUVERNAY
a6e90f6e8b Import Price Bug Fix 2026-06-03 09:41:52 +02:00
7df570c860 Demurrage 2026-06-03 09:20:44 +02:00
31d0e00f89 Demurrage 2026-06-03 08:43:05 +02:00
9a1673c6cd Demurrage 2026-06-03 08:37:21 +02:00
fb17c13a61 Demurrage 2026-06-03 08:28:32 +02:00
671251d23e Demurrage 2026-06-03 08:17:49 +02:00
4e7911b0ac Badge 2026-06-02 20:10:11 +02:00
c233e8cb77 Badge 2026-06-02 20:06:34 +02:00
de0ef9fdf4 Badge 2026-06-02 19:58:55 +02:00
c95b273291 Badge 2026-06-02 19:38:14 +02:00
542246baac Badge 2026-06-02 18:10:05 +02:00
0415ced262 Badge 2026-06-02 17:45:02 +02:00
b767f7648d Badge 2026-06-02 17:30:43 +02:00
405c2c2bf0 Merge dev 2026-06-02 14:59:23 +02:00
f39675543a Merge dev 2026-06-02 14:53:07 +02:00
2fecf60295 Notes 2026-06-02 12:00:51 +02:00
3b9a6ca943 Bug premium sale 2026-06-01 14:50:55 +02:00
fcd24af41b Clauses 2026-06-01 00:14:30 +02:00
b0d69207e4 Sale
Clauses
2026-05-31 23:59:55 +02:00
91bf2a3de4 Contract clauses 2026-05-31 23:39:07 +02:00
e9a8355587 Contract clauses 2026-05-31 23:33:25 +02:00
d483e7cf69 Charter party 2026-05-31 22:58:42 +02:00
2dd8360d62 Charter party 2026-05-31 22:33:55 +02:00
779e1aa361 Charter party 2026-05-31 22:07:57 +02:00
2223927d34 Charter party 2026-05-31 21:52:44 +02:00
a26d0516f0 Charter party 2026-05-31 21:47:25 +02:00
5605b19164 Charter party 2026-05-31 21:38:41 +02:00
f8d7f1e9d9 Charter party 2026-05-31 21:34:24 +02:00
735c5431af Premium composition 2026-05-31 20:50:23 +02:00
38c614af98 Pricing Fixed Component 2026-05-31 16:19:23 +02:00
fcd809a3e3 Add Fixed type component 2026-05-31 15:56:18 +02:00
83f7e43a6e Bug fee cog 2026-05-29 08:07:09 +02:00
a9b048d4cf Auto-calculation: formulas recalculate on cell change
- Template: calcMode=auto, calcOnSave=1
- VBA: removed xlCalculationManual, added ForceRefresh macro
  (clears cache + Application.Calculate for manual DB refresh)
- Workflow: change Setup!B6:B9 -> formulas recalculate automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 07:36:06 +02:00
7da99b3bf7 Live UDF formulas in template, remove gApiEnabled guard
- GL_Accounts_Client_Template.xlsx: C6:C13 now have real formulas
  =GetAccountAmount(Setup!$B$6,...) instead of text placeholders
- GLAccountsExcelApi.bas: removed gApiEnabled flag entirely, UDFs
  call RunQuery directly; add SetManualCalculation helper macro
- Works in Excel safe mode + manual calc: F9 triggers DB queries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:25:15 +02:00
242406588c Excel desktop POC: direct SQL via VBA, safe mode launcher
- GLAccountsExcelApi.bas: RefreshGLApi reads Setup!B6:B9 directly,
  calls PostgreSQL via ADODB without sheet.Evaluate or live UDF formulas
- Connection string pointed to vps107.geneva.hosting / postgres
- GL_Accounts_Client_Template.xlsx: fixed formula text cell refs (B6 not B7)
- Ouvrir_Excel_SafeMode.bat: launches Excel /safe with template to bypass
  print spooler freeze (splwow64 deadlock on edit mode entry)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:13:04 +02:00
3c4261ccf6 ICT bug 2026-05-28 20:18:24 +02:00
2428978d90 ICT bug padding + template 2026-05-28 20:12:39 +02:00
c9028f1590 Add maturity date 2026-05-28 13:59:36 +02:00
1af7ae99a4 Bank Template 2026-05-28 13:46:56 +02:00
26f43c2438 Template Melya 2026-05-28 13:34:56 +02:00
5b5685ae12 Template Melya 2026-05-28 13:10:34 +02:00
28c4fb3ff8 Bug Add Physical 2026-05-27 22:52:02 +02:00
181c4b3d34 Template Melya 2026-05-27 22:03:51 +02:00
50ac9f297c Template Melya 2026-05-27 21:20:20 +02:00
c2d4e34516 Mtm bug 2026-05-27 10:54:30 +02:00
5b5f1d77ff Matching bug 2026-05-27 09:45:16 +02:00
0fffb341fe Go to matching 2026-05-26 18:54:07 +02:00
8b4daeac84 SHipment cancel 2026-05-25 15:55:42 +02:00
4da56e4b8a Fee shipment bug 2026-05-25 14:49:27 +02:00
e7a1a83c90 Fee rules 2026-05-25 14:34:19 +02:00
c6d7b0da2b Fee rules 2026-05-25 14:17:08 +02:00
12727e53cb Fee rules 2026-05-25 13:51:55 +02:00
436d0b9a0c Fee rules 2026-05-25 13:36:11 +02:00
7a39754a63 Matching 2026-05-25 11:15:50 +02:00
55f342f106 Tolérance option 2026-05-25 11:01:45 +02:00
e78977dfa5 Pricing bug 2026-05-25 09:56:43 +02:00
ddca542bfc Pricing matrix 2026-05-25 09:04:24 +02:00
1e5611f726 Pricing 2026-05-25 08:18:59 +02:00
aed68ba6f4 GR 2026-05-24 19:43:54 +02:00
54309a1980 GR 2026-05-24 19:32:03 +02:00
1d398ab4b5 GR 2026-05-24 19:17:43 +02:00
2a5eec0e62 GR 2026-05-24 19:13:58 +02:00
20f51de50f GR 2026-05-24 19:04:58 +02:00
a22d185fb0 GR 2026-05-24 18:50:30 +02:00
390207a371 GR 2026-05-24 18:47:19 +02:00
01c23c1ec2 GR 2026-05-24 18:35:59 +02:00
c7146dd8a7 GR 2026-05-24 18:30:16 +02:00
7569d06254 GR 2026-05-24 18:26:33 +02:00
f0ed5b9bd3 GR 2026-05-24 18:19:57 +02:00
1075f49bd1 GR 2026-05-24 18:13:34 +02:00
1efaad8c38 GR 2026-05-24 18:05:31 +02:00
1dc8803287 GR 2026-05-24 17:53:33 +02:00
50636c1294 GR 2026-05-24 17:38:44 +02:00
6dfb12c125 Global reporting 2026-05-24 17:25:46 +02:00
96653be311 GL detail report 2026-05-21 20:34:01 +02:00
488fb15498 Import prices 2026-05-21 19:29:01 +02:00
f56f394ec3 Import price 2026-05-21 19:22:39 +02:00
4b9621a5f0 Notes 2026-05-21 14:20:51 +02:00
900d733fcd GL detail report 2026-05-21 14:06:16 +02:00
1adc4248ee GL detail report 2026-05-21 13:56:49 +02:00
3c5c20276d GL detail report 2026-05-21 13:47:51 +02:00
f16b334089 GL detail report 2026-05-21 13:39:21 +02:00
f29664fd77 Product origin Melya 2026-05-21 13:02:37 +02:00
f7e70e0438 COO 2026-05-20 16:43:02 +02:00
2cf1b25dad COO adj 2026-05-20 16:15:59 +02:00
27e081228f Price composition 2026-05-20 14:40:25 +02:00
ea3cb65d7d Price composition 2026-05-20 14:29:31 +02:00
9ad0217a69 Price composition 2026-05-20 14:17:36 +02:00
1f3616858a CreateContract 2026-05-20 14:04:22 +02:00
072960307a CreateContract 2026-05-20 14:00:32 +02:00
83612eeea3 Crop bug 2026-05-20 13:36:07 +02:00
8e16259ab2 Deal nb 2026-05-20 11:15:39 +02:00
3a1b9f2d90 Invoice template 2026-05-20 09:52:27 +02:00
22a083a853 Delta neg 2026-05-20 09:15:17 +02:00
c3d768d5a8 Create contract detail 2026-05-20 08:45:27 +02:00
133ca49eda GL detail 2026-05-19 21:34:23 +02:00
f8aded48af GL detail 2026-05-19 20:25:44 +02:00
c6a2d48e4c GL detail 2026-05-19 20:11:34 +02:00
22861dbc91 Targeted Qt 2026-05-19 11:12:52 +02:00
8ff380e229 GL report 2026-05-18 20:23:25 +02:00
80b2fed34b Bug terms 2026-05-17 20:33:20 +02:00
74e6cd0bf2 Targeted Qt 2026-05-17 20:22:08 +02:00
70f4bc291c Jauge 2026-05-17 19:30:55 +02:00
a231f32646 Targeted Qt 2026-05-17 19:22:30 +02:00
d1c0f4a738 Targeted Qt 2026-05-17 19:15:41 +02:00
ce883fa950 Targeted Qt 2026-05-17 19:03:51 +02:00
149306ff4e Targeted Qt 2026-05-17 18:56:23 +02:00
23fbcfa988 Docs 2026-05-17 18:43:09 +02:00
3a31cc354a Jauges bug 2026-05-17 18:28:39 +02:00
62941a5c3b Jauges Go to matching 2026-05-17 18:21:26 +02:00
d2ae65af82 Jauge tolerance 2026-05-17 18:00:36 +02:00
b35fa829e2 Jauge tolerance 2026-05-17 17:22:40 +02:00
9d436d6ea0 Matching 2026-05-17 15:45:54 +02:00
9a6bb83c19 Matching 2026-05-17 15:27:55 +02:00
ba79f88b43 Lots Management header 2026-05-17 11:33:21 +02:00
4d2f203469 Lots Management 2026-05-17 11:13:25 +02:00
c907a87b09 Go to matching action 2026-05-17 10:24:17 +02:00
ff75f9ab33 Go to matching action 2026-05-17 09:58:37 +02:00
3877ff0b6f Go to matching action 2026-05-17 09:50:22 +02:00
74b6425e93 Fees gap 2026-05-16 08:46:23 +02:00
b20c3d54a4 Bug check fee sql 2026-05-15 11:26:39 +02:00
60f26ddc9d Bug check qt 2026-05-14 21:52:10 +02:00
07ddb212e3 Check tolerance 2026-05-14 21:37:49 +02:00
4395884754 Fee check & rules 2026-05-14 19:32:17 +02:00
160ec1544b Bug Create contract 2026-05-14 17:11:54 +02:00
18c0329a94 Bug create contract 2026-05-14 16:59:09 +02:00
cdfc117220 Bug Create contract 2026-05-14 16:54:32 +02:00
a613f59a2d Bug create contract 2026-05-14 16:48:32 +02:00
4925cc8bfd Bug create contract 2026-05-14 16:40:23 +02:00
dd2b6952bc Bug create contract 2026-05-14 16:28:24 +02:00
30b9da1a78 Tolérance 2026-05-14 16:06:12 +02:00
1a041fdcbf Check qt 2026-05-14 15:42:59 +02:00
5f159b06dd Bug Add physical lot 2026-05-14 14:52:10 +02:00
30e0aa6543 Bug Add physical lot 2026-05-14 14:47:02 +02:00
20a4e20e91 Bug pnl fee 2026-05-14 14:31:23 +02:00
82a30f246d Bug check qt 2026-05-14 13:33:14 +02:00
e683ce7e62 Bug check qt 2026-05-14 12:32:31 +02:00
3c788d4764 bug check qt 2026-05-14 12:24:16 +02:00
b4d944b10c bug check qt 2026-05-14 12:18:31 +02:00
24826bec50 bug check qt 2026-05-14 11:52:53 +02:00
9677d0dae2 Bug quantity/th qt 2026-05-14 11:34:47 +02:00
2b3c823743 docs 2026-05-14 11:20:33 +02:00
355831c76d docs 2026-05-14 10:55:47 +02:00
e6bbf2f1c1 docs 2026-05-14 10:40:35 +02:00
0d034816f7 docs 2026-05-14 10:25:39 +02:00
b0a0d31528 notes 2026-05-13 20:46:58 +02:00
a85ab97d46 check qt 2026-05-13 20:28:40 +02:00
14ba0361d2 Check lot quantities 2026-05-13 19:29:24 +02:00
26c955ef53 check code + db 2026-05-13 18:39:54 +02:00
a71ce107f0 lot quantity logic 2026-05-13 17:39:13 +02:00
343fc8527c Add lot_hist readonly 2026-05-13 15:35:23 +02:00
de78da846d Business rules 2026-05-13 15:13:23 +02:00
50e25138fd business rules 2026-05-13 09:56:24 +02:00
f2c7adc2b3 index in docs 2026-05-11 21:14:16 +02:00
c017aac9a7 check qt create contract & matching 2026-05-11 20:42:26 +02:00
09c78395f1 Bug accrual fee + invoice sale side 2026-05-11 20:19:44 +02:00
4ae64269f4 notes 2026-05-10 22:05:33 +02:00
90593b70c2 Lost Management Mark as finished 2026-05-10 21:56:47 +02:00
4d5bbc5523 Hide field ITSA 2026-05-10 19:30:28 +02:00
a23aac4527 Fee currency 2026-05-10 18:45:02 +02:00
e37970a43d Invoice template 2026-05-10 16:13:02 +02:00
fe3a3ea71e Create contract 2026-05-10 16:00:38 +02:00
8e890f3c43 Notes 2026-05-09 18:11:55 +02:00
a684c7b9c1 Matching 2026-05-09 18:06:47 +02:00
28d3400651 Lots Management 2026-05-09 17:46:48 +02:00
000cf54fbc Lots Management 2026-05-09 17:23:54 +02:00
65fe45bae2 Lots Management 2026-05-09 16:27:29 +02:00
d8ac25dcda Lots Management Enhancement 2026-05-09 11:03:51 +02:00
c27c915700 notes 2026-05-06 15:15:52 +02:00
d4da45344c Remove lots 2026-05-06 09:40:53 +02:00
bfa874da43 Accounting notes 2026-05-05 20:14:42 +02:00
199caa2632 add country to sla cost 2026-05-01 16:32:37 +02:00
0f218374a7 Fee adj when th qt change 2026-05-01 15:12:22 +02:00
e03dee7def Th qt modification 2026-05-01 09:16:07 +02:00
83aa474073 rules 2026-04-30 15:38:27 +02:00
75bd34b673 Bug fee financing interest 2026-04-30 13:51:54 +02:00
03d65c253c Bug Pnl lot physic 2026-04-30 13:16:45 +02:00
adac3ca630 Rules 2026-04-29 21:54:36 +02:00
221e701507 Pnl sale side 2026-04-29 20:04:23 +02:00
5a7b5845b6 Pnl sale side open 2026-04-29 20:00:42 +02:00
616cf6d73c fee mode rate sale side 2026-04-29 19:47:59 +02:00
589ff16401 From To filter domain 2026-04-29 19:20:01 +02:00
a7d360b584 template coo 2026-04-29 18:54:23 +02:00
56b173d225 Valuation/ark as finished 2026-04-29 10:13:53 +02:00
27450d51f6 From To 2026-04-28 21:12:25 +02:00
ea31706ff8 From To & Price component 2026-04-28 21:06:15 +02:00
35fe191939 From To 2026-04-28 20:42:37 +02:00
062b42f83f Bug From To 2026-04-28 20:32:04 +02:00
debe374f1b traces 2026-04-28 20:16:38 +02:00
b5b554bad0 From TO line/sale line 2026-04-28 20:07:55 +02:00
1d6f3158c0 Price component filter domain & from to line/sale line 2026-04-28 19:59:33 +02:00
ff2b897b7a From to + pricing component 2026-04-28 19:48:29 +02:00
30d4362c09 Check From To + pricing component domain filter 2026-04-28 19:23:42 +02:00
aa9a18fb93 Bug qt doublée sale 2026-04-28 13:33:37 +02:00
4faa7fd83c bug padding 2026-04-28 12:19:06 +02:00
ca20d418cc Bug 2026-04-28 12:08:09 +02:00
3314d31fcd Bug 2026-04-28 11:57:28 +02:00
f6db19d1d9 Bug 2026-04-28 11:51:07 +02:00
257916bd6c Bug 2026-04-28 11:35:54 +02:00
2cdcbe7303 Bug 2026-04-28 11:27:35 +02:00
98893647f6 Bug 2026-04-28 11:13:30 +02:00
2e276e2b1f Move line manual 2026-04-28 11:09:37 +02:00
384c1285b1 Padding report 2026-04-27 20:09:36 +02:00
308252215f padding doc 2026-04-26 19:19:41 +02:00
7965b22954 padding acc 2026-04-26 15:28:35 +02:00
8ae2c7ca9c padding acc 2026-04-26 15:11:28 +02:00
16151381cc padding acc 2026-04-26 14:59:13 +02:00
4db0395a11 padding acc 2026-04-26 14:56:51 +02:00
fbfa73110f padding acc 2026-04-26 14:35:07 +02:00
9544418897 padding 2026-04-26 11:38:38 +02:00
e40a32e796 padding 2026-04-26 11:30:59 +02:00
530f2f9d97 padding 2026-04-26 10:51:06 +02:00
9879926f08 OS template account 2026-04-23 21:43:03 +02:00
04e9d7dc03 Fee FI bug 2026-04-23 14:22:24 +02:00
7a123b1af6 bug PL qt double * fee rate 2026-04-23 14:07:42 +02:00
a2b053c565 COO 2026-04-23 12:44:33 +02:00
6018429369 COO 2026-04-23 12:34:15 +02:00
4d4e7ab625 COO 2026-04-23 12:29:43 +02:00
68ce1f3a62 proforma adj 2026-04-23 11:10:05 +02:00
d8a56637c9 Bank accounts 2026-04-23 10:59:44 +02:00
a1486175f2 proforma template 2026-04-23 10:46:36 +02:00
228ee07a9c Add Our bank account 2026-04-23 10:15:20 +02:00
036a2801e5 COO report 2026-04-22 21:39:03 +02:00
c2838dcfd3 COO report 2026-04-22 21:26:55 +02:00
6e26f178ce coo report 2026-04-22 21:22:10 +02:00
4458871423 add COO 2026-04-22 21:09:54 +02:00
99782b5b0e Add packing list in account invoice 2026-04-22 20:36:14 +02:00
d09d183cdc Report name 2026-04-22 20:06:48 +02:00
ec45440400 Report name 2026-04-22 19:57:43 +02:00
edb23b008c patch lot sur shipment 2026-04-21 22:06:09 +02:00
fca279a038 bug DI 2026-04-21 21:21:29 +02:00
512862113a bug automation 2026-04-21 21:05:32 +02:00
1ed79a2363 BL nb 2026-04-21 19:39:49 +02:00
05a75f000a DI 2026-04-21 19:24:37 +02:00
77a5366622 DI 2026-04-21 19:19:01 +02:00
c203e1b300 DI 2026-04-21 19:12:38 +02:00
22ad8547e4 DI 2026-04-21 19:01:35 +02:00
2ea2165653 DI 2026-04-21 18:53:44 +02:00
5e72d4f98d document_incoming result_notes 2026-04-21 18:49:47 +02:00
b1680cc96c Add date + result_notes in document_incoming 2026-04-21 18:40:51 +02:00
cfb719a7da cron faircot 2026-04-21 14:59:31 +02:00
9e4a12605c cron faircot 2026-04-21 14:51:26 +02:00
4013f9d74c Correction cron faircot 2026-04-21 14:33:12 +02:00
280cff5fdb Pnl exclude Mark to finished & price on change 2026-04-21 13:22:41 +02:00
897c6f6824 Add constraint delete matched contracts 2026-04-20 10:29:33 +02:00
8906f00d36 Notes 2026-04-10 14:40:06 +02:00
29a719c117 template 2026-04-10 09:15:38 +02:00
d71257720e invoice seq nb 2026-04-10 08:40:53 +02:00
aee9c3277e Add bank to template 2026-04-10 08:24:59 +02:00
b68f475e22 Pricing manual 2026-04-10 07:52:59 +02:00
90eab73430 price component 2026-04-09 22:46:32 +02:00
472806ef06 pricing manuel 2026-04-09 22:38:48 +02:00
0def187750 Add manual pricing 2026-04-09 22:24:49 +02:00
229b6037fb unit correction 2026-04-09 21:55:40 +02:00
8a90216357 net gross 2026-04-09 21:46:28 +02:00
4bbd7a5e76 bug template 2026-04-09 21:38:26 +02:00
9c8d7f11ae kg lbs 2026-04-09 21:23:27 +02:00
b39607d987 del period 2026-04-09 20:58:47 +02:00
65482b4a8b bug Th qt 2026-04-09 20:31:24 +02:00
8b9787d4c0 bug th qt 2026-04-09 20:23:09 +02:00
a1ab7dec82 ICT bulk 2026-04-09 19:46:08 +02:00
5ae8af84fb Add note for bot 2026-04-08 17:15:11 +02:00
c90b14fcc1 Modification on Insurance template 2026-04-08 15:38:26 +02:00
e9ff9c76ab Add Bill template 2026-04-08 11:24:23 +02:00
63d8266a9c Add Bill template 2026-04-08 10:26:25 +02:00
add4cdc137 Add Packing list template 2026-04-08 10:08:17 +02:00
4d94aa78ed Merge branch 'main' of https://gitea.open-squared.tech/admin/tradon 2026-04-08 09:46:05 +02:00
da01249f66 Add Packing list template 2026-04-08 09:46:01 +02:00
AzureAD\SylvainDUVERNAY
5cbb57c657 VSP and SSH configuration 2026-04-07 15:35:06 +02:00
50a8c6328f Doc deploiement 2026-04-07 15:03:37 +02:00
eaa5c8b544 Add Payment order template 2026-04-07 14:15:10 +02:00
78e9e06a8b Add Payment order template 2026-04-07 13:59:16 +02:00
9f06398b2c Th qt correction 2026-04-07 13:42:17 +02:00
51a84f1f2e Add Payment order 2026-04-07 11:54:18 +02:00
00330008d1 Add Purchase order template 2026-04-07 11:42:09 +02:00
3480eb8a7a Add surveyor on shipment 2026-04-07 11:09:52 +02:00
5179d98289 Add insured amount 2026-04-07 10:54:14 +02:00
2109d7a3e4 Add insured amount 2026-04-07 10:32:29 +02:00
1f350e6207 Add session notes 2026-04-06 18:52:15 +02:00
7722292482 Add Insurance template 2026-04-06 18:45:05 +02:00
ec359f6b8a Add insurance template 2026-04-06 17:30:50 +02:00
845b9cf749 Add bank bloc 2026-04-06 16:45:53 +02:00
48b941b109 Add bank in template 2026-04-06 16:29:44 +02:00
18ece66cdb Add bank bloc on template 2026-04-06 16:18:07 +02:00
acfc2fe88a Add invoice_melya.fodt 2026-04-06 15:56:00 +02:00
888b880bd6 Add template management 2026-04-06 15:17:17 +02:00
1f62ae91dd Add multi client template management 2026-04-06 14:56:37 +02:00
05e68636ad Add WR draft 2026-04-06 11:17:24 +02:00
bfb9bb3188 Add WR draft management 2026-04-06 11:02:13 +02:00
b78e64f9f1 Add counter 2026-04-06 09:50:15 +02:00
199b8aec12 Add counter to controller 2026-04-06 09:03:10 +02:00
1757075f2b 03.04.26 2026-04-03 07:39:52 +02:00
172d38479d 02.04.26 2026-04-02 17:28:44 +02:00
4902368b15 02.04.26 2026-04-02 17:20:36 +02:00
7b4f757cb5 02.04.26 2026-04-02 17:12:53 +02:00
b37f132cdf 02.04.26 2026-04-02 17:07:58 +02:00
15f791bd92 02.04.26 2026-04-02 16:46:50 +02:00
58cd66e543 02.04.26 2026-04-02 16:31:05 +02:00
cc6ce82ec1 02.04.26 2026-04-02 16:24:18 +02:00
11526ef3ee 02.04.26 2026-04-02 13:16:17 +02:00
6d52317804 02.04.26 2026-04-02 13:09:04 +02:00
a99efcfc5b 02.04.26 2026-04-02 13:00:19 +02:00
0d5cf7dffc 02.04.26 2026-04-02 12:46:42 +02:00
613b679908 02.04.26 2026-04-02 12:32:06 +02:00
51ced23ab8 02.04.26 2026-04-02 12:12:49 +02:00
2958e1fb9e 02.04.26 2026-04-02 11:33:49 +02:00
346a34951d 02.04.26 2026-04-02 11:16:26 +02:00
b644aea007 02.04.26 2026-04-02 11:03:58 +02:00
5dbaba5f32 02.04.26 2026-04-02 11:00:32 +02:00
d133665fc7 02.04.26 2026-04-02 10:38:00 +02:00
c2cb2a874c 02.04.26 2026-04-02 10:32:36 +02:00
408970c339 01.04.26 2026-04-01 21:48:31 +02:00
ea2627c9ae 01.04.26 2026-04-01 21:40:32 +02:00
ac988a714a 01.04.26 2026-04-01 21:26:46 +02:00
97eae6e4a6 01.04.26 2026-04-01 21:19:31 +02:00
3976b387d7 01.04.26 2026-04-01 18:29:44 +02:00
9b8e8127a1 01.04.26 2026-04-01 18:02:09 +02:00
f53a9bce27 01.04.26 2026-04-01 15:22:21 +02:00
a7753b974f 01.04.26 2026-04-01 14:30:04 +02:00
c687828ba5 01.04.26 2026-04-01 14:09:42 +02:00
5054b64cd0 01.04.26 2026-04-01 11:08:02 +02:00
06922973b7 01.04.26 2026-04-01 10:03:19 +02:00
18ebf7f06c 01.04.26 2026-04-01 09:53:40 +02:00
44c4560f24 01.04.26 2026-04-01 09:47:03 +02:00
7643bf21fb 31.03.26 2026-03-31 17:54:23 +02:00
97677025d7 31.03.26 2026-03-31 17:31:23 +02:00
02fe5b3e5d 31.03.26 2026-03-31 17:28:13 +02:00
6e529deca0 31.03.26 2026-03-31 17:20:49 +02:00
efee365fc6 31.03.26 2026-03-31 17:17:12 +02:00
6bf245ac64 Merge pull request 'dev' (#8) from dev into main
Reviewed-on: #8
2026-03-31 09:57:16 +00:00
238869989a 30.03.26 2026-03-30 19:33:34 +02:00
AzureAD\SylvainDUVERNAY
806e374ceb Commit 2026-03-29 17:48:47 +02:00
435 changed files with 112286 additions and 8044 deletions

View File

@@ -1,17 +0,0 @@
{
"permissions": {
"allow": [
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules)",
"Bash(ls -d */)",
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade)",
"Bash(ls -1d */)",
"Bash(for f:*)",
"Bash(do echo:*)",
"Read(//c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/**)",
"Bash(done)",
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade/view)",
"Bash(ls -1 *.xml)",
"Bash(py --version)"
]
}
}

5
.gitignore vendored
View File

@@ -1,2 +1,3 @@
deployment/vps-TradonDev_Instructions.md
deployment/vps/46.202.173.47-credentials.md
*.pyc
~$*
notes/accounting/excel_web_api/.env

View File

@@ -38,15 +38,55 @@ Guide rapide pour les agents qui codent dans ce repository.
- Lire `wsgi.py`, `rpc.py`, `protocols/*`, `tests/test_rpc.py`, `tests/test_wsgi.py`.
- Si bug metier:
- Modifier uniquement `modules/<module>/` + ses tests.
- Conventions de champs dates:
- Dans ce projet, ne pas introduire de `fields.DateTime`.
- Utiliser `fields.Date` pour les dates metier et les champs de suivi UI, sauf demande explicite deja existante dans le module cible.
- Si bug template Relatorio (`.fodt`):
- Lire d'abord le template standard voisin du meme domaine (`invoice.fodt`, `sale.fodt`, etc.).
- Preferer des proprietes Python simples exposees par le modele plutot que des expressions Genshi complexes dans le template.
- Dans les placeholders XML, utiliser `&quot;` et `&apos;` plutot que des antislashs type `\'`.
- Si un document facture depend fortement d'une vente/achat, ajouter au besoin un petit pont Python pour exposer des `report_*` stables au template.
- Pour les templates `stock.shipment.in`, preferer aussi des proprietes `report_*` sur le shipment plutot que des contextes ad hoc (`si_*`) quand le document devient metier ou client-specifique.
- Si plusieurs actions de report pointent vers `report_name = 'account.invoice'`, verifier aussi le cache `invoice_report_cache` dans `modules/account_invoice/invoice.py`: un mauvais cache peut faire croire que plusieurs actions utilisent le meme `.fodt`.
- Avant de conclure qu'un template ou une action est faux, verifier si le report alternatif doit bypasser le cache standard.
- Pour les templates shipment, ne pas supposer qu'une variable locale comme `shipment` sera definie partout dans Genshi, surtout dans les headers/footers; preferer `records[0]....` ou des placeholders alignes sur le scope reel du report.
- Dans `purchase_trade`, pour remonter d'une facture vers shipment, pro forma, freight ou autres donnees logistiques, privilegier le lot physique comme pont entre `purchase.line`, `sale.line` et shipment.
- Pour `FREIGHT VALUE`, ne pas lire un champ direct sur la facture: retrouver le fee de shipment (`shipment_in`) dont le produit est `Maritime freight`, puis utiliser `fee.get_amount()`.
- Rappels session templates (2026-04-08):
- `insurance.fodt`: le texte "insured for account of" doit afficher la compagnie courante (shipment.company.party), pas le client.
- `insurance.fodt`: exposer des proprietes Python `report_*` sur `stock.shipment.in` pour les montants (incoming moves) et les zones client-specifiques.
- `insurance.fodt`: "Amount insured" suit la regle metier 110% du montant incoming (base calculee via lot -> purchase.line.unit_price * quantite courante convertie).
- `insurance.fodt`: zone "Contact the following surveyor" alimentee par une propriete dediee, avec champ `surveyor` (party.party) cote shipment.
- `packing_list.fodt`: date en haut a droite = date du jour; unites Net/Gross = unite de `purchase.line`.
- `bill.fodt` (sale): la 2eme date doit etre une vraie maturity date (depuis `invoice.lines_to_pay.maturity_date`), pas `payment_term.rec_name`.
- `bill.fodt` (sale): le montant en lettres doit provenir du montant du bill (facture/total), pas du `unit_price` de ligne.
- Quand un template affiche les placeholders en brut, verifier que les champs sont bien des placeholders Relatorio dans le XML (pas du texte litteral).
- Eviter les apostrophes echappees style `\'` dans placeholders; preferer `&quot;` et `&apos;`.
## 4.bis) Memos metier et templates
- Regles metier transverses:
- `notes/business_rules.md`
- Regles metier locales `purchase_trade`:
- `modules/purchase_trade/docs/business-rules.md`
- `modules/purchase_trade/docs_source/business/` pour les sources de verite
des pages business publiees dans le wiki
- `modules/purchase_trade/docs/business/` pour les pages generees lues par le
wiki
- Decisions templates / reports:
- `notes/template_business_rules.md`
- Documentation comptable et reporting:
- `notes/accounting/README.md`
- `notes/accounting/gap_analysis_workflow.md`
- `notes/accounting/reporting.md`
- `notes/accounting/business_rules.md`
- `notes/accounting/purchase_trade_extensions.md`
- Regles sensibles `purchase_trade` a relire avant de toucher lots, quantites
ou fees:
- `modules/purchase_trade/AGENTS.md`
- `modules/purchase_trade/docs_source/business/lots-and-quantities.md`
- `modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021
(`quantity_theorical`, `lot.qt`, lots physiques, fees et PnL fee).
## 5) Workflow de modification (obligatoire)
@@ -105,3 +145,18 @@ Toujours fournir:
3. Proposer le patch minimal.
4. Implementer + tester cible.
5. Rendre avec le contrat de sortie (section 8).
- Rappels session 2026-04-09:
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: poids et unites depuis `lot.qt.hist` / `lot_unit_line`, priorite lots `physic`, sinon lot `virtual` unique.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: infos shipment depuis les lots reels des lignes facture; ne rien afficher si plusieurs shipments differents.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: `S/I` = `shipment.reference`; `NB BALES: 0` => `Unchanged` sur le final.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: quantites uniformisees a `2` decimales; conversion `LBS` via UoM, jamais via un facteur fixe aveugle.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: si plusieurs lignes reutilisent le meme lot, les lignes detaillees suivent la quantite facturee convertie, mais le `GROSS` global doit rester le vrai delta historique du lot.
- `sale_ict.fodt`: meme priorite lots; les mots suivent l'unite reelle; le total convertit vers une unite commune, qui est celle du lot virtuel seulement s'il y a un seul lot virtuel sur tout le report.
- `lot.report.r_del_period`: utiliser `sale.line.del_period` pour `lot_s` sans `lot_p`, sinon `purchase.line.del_period`.
- `lot.do_weighing`: `lot_qt` editable et ecrasement direct de `lot.lot_qt`.
- `account.invoice`: `Validate` cree aussi le `account.move` pour les factures client, attribue aussi le `number` a ce stade pour les factures client comme fournisseur; `Post` ne doit plus forcer une fresh session sur ce flux.
- `pricing.pricing`: saisie manuelle autorisee meme sans composant; en manuel, l'utilisateur saisit seulement `Qt` et `Settl. price`; `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et `eod_price` sont derives automatiquement.
- `pricing.pricing`: en manuel, `fixed_qt` = cumul des `quantity`, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`, `unfixed_qt` = reste a fixer, `unfixed_qt_price` = `settl_price` de la ligne.
- `pricing.pricing`: `eod_price` reste non editable et calcule en prix moyen pondere; `last=True` gere par groupe `line + component`, choisi sur la `pricing_date` la plus grande.
- `purchase_trade`: `trader` filtre sur `TRADER`, `operator` sur `OPERATOR`; fallback sur `quantity` si `quantity_theorical` est vide dans les quotas/pricings.
- `sale.line` / `purchase.line`: en mode `basis`, sans `price_component`, le `Price` et le `Fix. progress` de la ligne doivent remonter depuis la ligne `Summary` sans component.

1
debug.log Normal file
View File

@@ -0,0 +1 @@
[0407/143111.471:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: Accès refusé. (0x5)

View File

@@ -0,0 +1,36 @@
# Fiche VPS - 46.202.173.47
Date de reference: 2026-04-07
## Identite serveur
- IP: `46.202.173.47`
- Hostname alias conseille: `vps3`
## Acces
- Cle publique Laurent Barontini (vps-deploy):
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEm8JMCYsk6I1IoYhIHXNrdyERHdh+eeDCJagOHaRAEK vps-deploy`
- Cle publique Sylvain Duvernay (s.duvernay@singa-associates.com):
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6Xsp/v6q6JO04ETv1880qoSPptUMxlWQvgcBz67o63 s.duvernay@singa-associates.com`
- Fichier local: `$env:USERPROFILE\.ssh\id_ed25519`
- Mot de passe fourni:
`!!OpenSquared!!`
- Utilisateur SSH:
'root'
- Port SSH:
'22'
## Commande de connexion type
- Laurent Barontini (cle vps-deploy):
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@46.202.173.47`
- Sylvain Duvernay (cle id_ed25519):
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47`
- Avec port custom:
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 -p <port> <user>@46.202.173.47`

View File

@@ -1,3 +0,0 @@
Serveur: 'VPS-62.72.36.116'
Alias Name: 'VPS DEV'
IP Address:'62.72.36.116'

View File

@@ -1538,6 +1538,10 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
})
action = fields.Function(
fields.Char("Action"), 'get_action', searcher='search_action')
old_values = fields.Text("Old Values", readonly=True)
new_values = fields.Text("New Values", readonly=True)
change_summary = fields.Function(
fields.Text("Changes"), 'get_change_summary')
@classmethod
def __setup__(cls):
@@ -1583,6 +1587,172 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
return f'{field_name} : {state}'
return self.target
@staticmethod
def _load_change_values(value):
if not value:
return {}
try:
return json.loads(value)
except (TypeError, ValueError):
return {}
@staticmethod
def _format_change_value(value):
if isinstance(value, list):
if len(value) > 6:
return '[%s items]' % len(value)
return ', '.join(Log._format_change_value(v) for v in value)
if isinstance(value, dict):
return json.dumps(value, sort_keys=True, ensure_ascii=False)
if value in (None, ''):
return ''
return str(value)
@staticmethod
def _singular_label(label):
if label.endswith('ies'):
return label[:-3] + 'y'
if label.endswith('s'):
return label[:-1]
return label
def _nested_record_label(self, field, field_label, record_id):
record_label = self._singular_label(field_label)
if not self.resource:
return '%s %s' % (record_label, record_id)
records = list(getattr(self.resource, field.name, None) or [])
line_records = [
record for record in records
if getattr(record, 'type', 'line') == 'line']
ordered_records = line_records or records
for index, record in enumerate(ordered_records, 1):
if str(getattr(record, 'id', '')) == str(record_id):
return '%s %s' % (record_label, index)
return '%s %s' % (record_label, record_id)
def _format_nested_change_summary(
self, field, field_label, target_model, old_value, new_value):
pool = Pool()
Field = pool.get('ir.model.field')
lines = []
if not isinstance(old_value, dict) or not isinstance(new_value, dict):
return lines
def record_sort_key(value):
try:
return int(value)
except (TypeError, ValueError):
return value
for record_id in sorted(
set(old_value) | set(new_value), key=record_sort_key):
record_old = old_value.get(record_id, {})
record_new = new_value.get(record_id, {})
if not isinstance(record_old, dict) or not isinstance(
record_new, dict):
continue
for child_field in sorted(set(record_old) | set(record_new)):
child_old = record_old.get(child_field)
child_new = record_new.get(child_field)
if child_old == child_new:
continue
child_label = Field.get_name(target_model, child_field)
lines.append('%s %s %s => %s' % (
self._nested_record_label(
field, field_label, record_id),
child_label,
self._format_change_value(child_old),
self._format_change_value(child_new)))
return lines
@staticmethod
def _iter_change_commands(value):
if not isinstance(value, list):
return
for item in value:
if isinstance(item, list) and item and isinstance(item[0], str):
command = item[0]
if len(item) >= 3:
yield command, item[1], item[2]
elif len(item) >= 2:
yield command, item[1], None
if value and all(isinstance(item, list) for item in value):
return
index = 0
while index < len(value):
command = value[index]
if not isinstance(command, str):
index += 1
continue
if command == 'write':
if index + 2 >= len(value):
break
yield command, value[index + 1], value[index + 2]
index += 3
continue
if index + 1 >= len(value):
break
yield command, value[index + 1], None
index += 2
def _format_command_change_summary(
self, field, field_label, target_model, new_value):
pool = Pool()
Field = pool.get('ir.model.field')
lines = []
for command, record_ids, values in self._iter_change_commands(
new_value) or []:
if command != 'write' or not isinstance(values, dict):
continue
for record_id in record_ids:
for child_field, child_value in sorted(values.items()):
child_label = Field.get_name(target_model, child_field)
lines.append('%s %s => %s' % (
self._nested_record_label(
field, field_label, record_id),
child_label,
self._format_change_value(child_value)))
return lines
def get_change_summary(self, name):
if self.event != 'write':
return ''
old_values = self._load_change_values(self.old_values)
new_values = self._load_change_values(self.new_values)
if not old_values and not new_values:
return ''
pool = Pool()
Field = pool.get('ir.model.field')
model = self.resource.__name__ if self.resource else None
lines = []
for field_name in sorted(set(old_values) | set(new_values)):
old_value = old_values.get(field_name)
new_value = new_values.get(field_name)
if old_value == new_value:
continue
if model:
field_label = Field.get_name(model, field_name)
else:
field_label = field_name
field = (
getattr(self.resource.__class__, field_name, None)
if self.resource else None)
if field and hasattr(field, 'get_target'):
target_model = field.get_target().__name__
nested_lines = self._format_nested_change_summary(
field, field_label, target_model, old_value, new_value)
if nested_lines:
lines.extend(nested_lines)
continue
command_lines = self._format_command_change_summary(
field, field_label, target_model, new_value)
if command_lines:
lines.extend(command_lines)
continue
lines.append('%s: %s -> %s' % (
field_label,
self._format_change_value(old_value),
self._format_change_value(new_value)))
return '\n'.join(lines)
@classmethod
def search_action(cls, name, clause):
return [('target', *clause[1:])]

View File

@@ -73,6 +73,7 @@ attlist.field &=
| "text"
| "time"
| "timedelta"
| "tolerance_gauge"
| "url"
}?
attlist.field &= attribute fill { "0" | "1" }?
@@ -122,6 +123,12 @@ attlist.field &= attribute symbol { text }?
attlist.field &= [a:defaultValue = "1"] attribute grouping { "0" | "1" }?
attlist.field &= [a:defaultValue = "square"] attribute border { "square" | "circle" | "rounded" }?
attlist.field &= attribute loading { "lazy" | "eager" }?
attlist.field &= attribute min { text }?
attlist.field &= attribute max { text }?
attlist.field &= attribute min_field { text }?
attlist.field &= attribute max_field { text }?
attlist.field &= attribute center { text }?
attlist.field &= attribute digits { text }?
image = element image { attlist.image, empty }
attlist.image &= attribute name { text }
attlist.image &= [a:defaultValue = "icon"] attribute type { "icon" | "url" }?

View File

@@ -251,6 +251,7 @@
<value>text</value>
<value>time</value>
<value>timedelta</value>
<value>tolerance_gauge</value>
<value>url</value>
</choice>
</attribute>
@@ -577,6 +578,54 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">center</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">digits</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>

View File

@@ -42,6 +42,7 @@ attlist.field &=
| "text"
| "time"
| "timedelta"
| "tolerance_gauge"
| "url"
}?
attlist.field &=
@@ -75,6 +76,12 @@ attlist.field &= attribute help_field { text }?
attlist.field &= attribute view_ids { text }?
attlist.field &= attribute symbol { text }?
attlist.field &= [a:defaultValue = "1"] attribute grouping { "0" | "1" }?
attlist.field &= attribute min { text }?
attlist.field &= attribute max { text }?
attlist.field &= attribute min_field { text }?
attlist.field &= attribute max_field { text }?
attlist.field &= attribute center { text }?
attlist.field &= attribute digits { text }?
prefix = element prefix { attlist.affix, empty }
suffix = element suffix { attlist.affix, empty }
attlist.affix &= attribute string { text }?

View File

@@ -117,6 +117,7 @@
<name ns="">widget</name>
<choice>
<value>binary</value>
<value>badge</value>
<value>boolean</value>
<value>callto</value>
<value>char</value>
@@ -139,11 +140,21 @@
<value>text</value>
<value>time</value>
<value>timedelta</value>
<value>tolerance_gauge</value>
<value>variation</value>
<value>url</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">badge_colors</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
@@ -198,6 +209,52 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">sum_previous</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
<name ns="">sum_variation</name>
<choice>
<value>0</value>
<value>1</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">variation_previous</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">variation_group_by</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
<name ns="">variation_summary</name>
<choice>
<value>0</value>
<value>1</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
@@ -330,6 +387,54 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">center</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">digits</name>
<text/>
</attribute>
</optional>
</define>
<define name="prefix">
<element>
<name ns="">prefix</name>

View File

@@ -13,4 +13,10 @@ this repository contains the full copyright notices and license terms. -->
<field name="event"/>
<field name="action"/>
</group>
<label name="change_summary"/>
<field name="change_summary"/>
<label name="old_values"/>
<field name="old_values"/>
<label name="new_values"/>
<field name="new_values"/>
</form>

View File

@@ -8,4 +8,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="user" expand="1"/>
<field name="event"/>
<field name="action" expand="1"/>
<field name="change_summary" optional="1"/>
</tree>

View File

@@ -5,6 +5,7 @@ import base64
import csv
import datetime
import decimal
import json
import random
import time
import warnings
@@ -193,6 +194,99 @@ class ModelStorage(Model):
resource=record, event=event, target=target, user=user,
**extra))
@staticmethod
def _json_log_value(value):
if isinstance(value, (datetime.date, datetime.datetime, datetime.time)):
return value.isoformat()
if isinstance(value, decimal.Decimal):
return str(value)
if isinstance(value, bytes):
return '<binary>'
if isinstance(value, Model):
return str(value)
if isinstance(value, (list, tuple)):
return [ModelStorage._json_log_value(v) for v in value]
if isinstance(value, dict):
return {
str(k): ModelStorage._json_log_value(v)
for k, v in value.items()
}
return value
@classmethod
def _log_value_text(cls, values):
values = {
name: cls._json_log_value(value)
for name, value in values.items()
}
return json.dumps(values, sort_keys=True, ensure_ascii=False)
@staticmethod
def _iter_log_commands(value):
if not isinstance(value, (list, tuple)):
return
for item in value:
if (isinstance(item, (list, tuple))
and item and isinstance(item[0], str)):
command = item[0]
if len(item) >= 3:
yield command, item[1], item[2]
elif len(item) >= 2:
yield command, item[1], None
if value and all(isinstance(item, (list, tuple)) for item in value):
return
index = 0
while index < len(value):
command = value[index]
if not isinstance(command, str):
index += 1
continue
if command == 'write':
if index + 2 >= len(value):
break
yield command, value[index + 1], value[index + 2]
index += 3
continue
if index + 1 >= len(value):
break
yield command, value[index + 1], None
index += 2
@classmethod
def _nested_log_values(cls, values):
nested_old = {}
nested_new = {}
for field_name, value in values.items():
field = cls._fields.get(field_name)
if not field or not hasattr(field, 'get_target'):
continue
commands = list(cls._iter_log_commands(value) or [])
write_commands = [
(ids, command_values)
for command, ids, command_values in commands
if command == 'write' and command_values]
if not write_commands:
continue
Target = field.get_target()
field_old = {}
field_new = {}
for ids, command_values in write_commands:
target_ids = [int(i) for i in ids]
field_names = list(sorted(command_values.keys()))
with without_check_access():
rows = Target.read(target_ids, field_names)
for row in rows:
record_id = str(row['id'])
field_old.setdefault(record_id, {}).update({
name: row.get(name)
for name in field_names
})
field_new.setdefault(record_id, {}).update(command_values)
if field_old or field_new:
nested_old[field_name] = field_old
nested_new[field_name] = field_new
return nested_old, nested_new
@classmethod
def create(cls, vlist):
'''
@@ -267,7 +361,25 @@ class ModelStorage(Model):
all_records += records
all_fields.update(values.keys())
if transaction.check_access and values:
cls.log(records, 'write', ','.join(sorted(values.keys())))
fields_names = list(sorted(values.keys()))
with without_check_access():
old_values = {
row['id']: {
name: row.get(name)
for name in fields_names
}
for row in cls.read(
[r.id for r in records], fields_names)}
nested_old, nested_new = cls._nested_log_values(values)
for record in records:
record_old_values = old_values.get(record.id, {})
record_new_values = values.copy()
record_old_values.update(nested_old)
record_new_values.update(nested_new)
cls.log([record], 'write', ','.join(fields_names),
old_values=cls._log_value_text(
record_old_values),
new_values=cls._log_value_text(record_new_values))
ModelAccess.check(cls.__name__, 'write')
ModelFieldAccess.check(cls.__name__, all_fields, 'write')

10
modules/AGENTS.md Normal file
View File

@@ -0,0 +1,10 @@
# AGENTS.md - Regles globales des modules
Ce guide complete le `AGENTS.md` racine et s'applique a tous les modules sous `modules/`.
## Regles transversales
- Ne pas introduire de `fields.DateTime` dans les modules metier sauf demande explicite et code cible deja base sur ce type.
- Pour afficher une date issue d'un champ technique comme `create_date`, preferer un champ fonctionnel en `fields.Date` plutot qu'un acces direct en vue.
- Si une vue doit afficher une information technique non declaree explicitement sur le modele, ajouter un champ fonctionnel dedie cote Python avant de modifier le XML.

View File

@@ -30,6 +30,8 @@ def register():
account.GeneralLedgerAccountParty,
account.GeneralLedgerLine,
account.GeneralLedgerLineContext,
account.GeneralLedgerDetail,
account.GeneralLedgerDetailContext,
account.BalanceSheetContext,
account.BalanceSheetComparisionContext,
account.IncomeStatementContext,
@@ -118,6 +120,7 @@ def register():
Pool.register(
account.AccountTypeStatement,
account.GeneralLedger,
account.GeneralLedgerDetailReport,
account.TrialBalance,
account.AgedBalanceReport,
move.GeneralJournal,

View File

@@ -7,9 +7,10 @@ from decimal import Decimal
from itertools import zip_longest
from dateutil.relativedelta import relativedelta
from sql import Column, Literal, Null, Window
from sql import Column, Literal, Null, Union, Window
from sql.aggregate import Count, Max, Min, Sum
from sql.conditionals import Case, Coalesce
from sql.operators import Concat
from trytond import backend
from trytond.i18n import gettext
@@ -2583,6 +2584,562 @@ class GeneralLedgerLineContext(GeneralLedgerAccountContext):
return super().get_context(fields_names=fields_names)
class GeneralLedgerDetailContext(GeneralLedgerAccountContext):
'General Ledger Detail Context'
__name__ = 'account.general_ledger.detail.context'
account = fields.Many2One(
'account.account', "Account",
domain=[
('company', '=', Eval('company')),
('type', '!=', None),
('closed', '!=', True),
],
depends=['company'])
account_from = fields.Char("Account From")
account_to = fields.Char("Account To")
currency = fields.Many2One('currency.currency', "Transaction Currency")
party = fields.Many2One(
'party.party', "Party",
context={
'company': Eval('company', -1),
},
depends=['company'])
document_number = fields.Char("Document Number")
@classmethod
def default_account(cls):
return Transaction().context.get('account')
@classmethod
def default_account_from(cls):
return Transaction().context.get('account_from')
@classmethod
def default_account_to(cls):
return Transaction().context.get('account_to')
@classmethod
def default_currency(cls):
return Transaction().context.get('currency')
@classmethod
def default_party(cls):
return Transaction().context.get('party')
@classmethod
def default_document_number(cls):
return Transaction().context.get('document_number')
@classmethod
def get_context(cls, fields_names=None):
fields_names = fields_names.copy() if fields_names is not None else []
fields_names += [
'account', 'account_from', 'account_to', 'currency', 'party',
'document_number']
return super().get_context(fields_names=fields_names)
class GeneralLedgerDetail(DescriptionOriginMixin, ModelSQL, ModelView):
'General Ledger Detail'
__name__ = 'account.general_ledger.detail'
row_type = fields.Selection([
('opening', "Opening"),
('movement', "Movement"),
('closing', "Closing"),
], "Row Type", sort=False)
row_sequence = fields.Integer("Row Sequence")
company = fields.Many2One('company.company', "Company")
account = fields.Many2One('account.account', "Account")
account_code = fields.Char("Account Code")
account_name = fields.Char("Account Name")
transaction_currency = fields.Many2One(
'currency.currency', "Transaction Currency")
base_currency = fields.Many2One('currency.currency', "Base Currency")
posting_date = fields.Date("Posting Date")
journal = fields.Many2One('account.journal', "Journal")
journal_entry_number = fields.Char("Journal Entry Number")
document_number = fields.Function(
fields.Char("Document Number"), 'get_document_field')
voucher_number = fields.Char("Voucher Number")
document_type = fields.Function(
fields.Char("Document Type"), 'get_document_field')
document_date = fields.Date("Document Date")
posting_status = fields.Selection([
('draft', "Draft"),
('posted', "Posted"),
], "Posting Status", sort=False)
party = fields.Many2One('party.party', "Party")
move = fields.Many2One('account.move', "Move")
move_line = fields.Many2One('account.move.line', "Move Line")
origin = fields.Reference('Origin', selection='get_origin')
description = fields.Char("Line Desc.")
move_description = fields.Char("Description")
reference = fields.Char("Reference")
debit_base_currency = fields.Numeric(
"Debit Base Currency", digits=(16, 2))
credit_base_currency = fields.Numeric(
"Credit Base Currency", digits=(16, 2))
balance_base_currency = fields.Numeric(
"Balance Base Currency", digits=(16, 2))
running_balance_base_currency = fields.Numeric(
"Running Balance Base Currency", digits=(16, 2))
debit_transaction_currency = fields.Numeric(
"Debit Transaction Currency", digits=(16, 2))
credit_transaction_currency = fields.Numeric(
"Credit Transaction Currency", digits=(16, 2))
balance_transaction_currency = fields.Numeric(
"Balance Transaction Currency", digits=(16, 2))
running_balance_transaction_currency = fields.Numeric(
"Running Balance Transaction Currency", digits=(16, 2))
payable_qty = fields.Numeric("Quantity", digits=(16, 5))
uom = fields.Char("UoM")
entered_by = fields.Many2One('res.user', "Entered By")
entered_date = fields.Date("Entered Date")
modified_by = fields.Many2One('res.user', "Modified By")
modified_date = fields.Date("Modified Date")
@classmethod
def __setup__(cls):
super().__setup__()
cls._order = [
('account_code', 'ASC'),
('transaction_currency', 'ASC'),
('row_sequence', 'ASC'),
('posting_date', 'ASC'),
('journal_entry_number', 'ASC'),
('id', 'ASC'),
]
@classmethod
def _get_period_ids(cls, name, context):
pool = Pool()
Period = pool.get('account.period')
period = None
if name == 'opening':
period_ids = []
if context.get('start_period'):
period = Period(context['start_period'])
elif name == 'closing':
period_ids = []
if context.get('end_period'):
period = Period(context['end_period'])
else:
periods = Period.search([
('fiscalyear', '=', context.get('fiscalyear')),
('type', '=', 'standard'),
],
order=[('start_date', 'DESC')], limit=1)
if periods:
period, = periods
else:
start_period_ids = set(cls._get_period_ids('opening', context))
end_period_ids = set(cls._get_period_ids('closing', context))
return list(end_period_ids.difference(start_period_ids))
if period:
if name == 'opening':
date_clause = ('end_date', '<=', period.start_date)
else:
date_clause = [
('end_date', '<=', period.end_date),
('start_date', '<', period.end_date),
]
periods = Period.search([
('fiscalyear', '=', context.get('fiscalyear')),
date_clause,
])
if period.start_date == period.end_date:
periods.append(period)
if periods:
period_ids = [p.id for p in periods]
if name == 'closing':
period_ids.append(period.id)
return period_ids
@classmethod
def _get_dates(cls, name, context):
if name == 'opening':
to_date = context.get('from_date') or datetime.date.min
if to_date:
try:
to_date -= datetime.timedelta(days=1)
except OverflowError:
pass
return None, to_date
elif name == 'closing':
return None, context.get('to_date')
return context.get('from_date'), context.get('to_date')
@classmethod
def _query_context(cls, name, context):
if context.get('start_period') or context.get('end_period'):
return {
'periods': cls._get_period_ids(name, context),
'from_date': None,
'to_date': None,
}
elif context.get('from_date') or context.get('to_date'):
from_date, to_date = cls._get_dates(name, context)
return {
'periods': None,
'from_date': from_date,
'to_date': to_date,
}
else:
if name == 'opening':
return {
'periods': [],
'from_date': None,
'to_date': None,
}
return {
'periods': None,
'from_date': None,
'to_date': None,
}
@classmethod
def _extra_where(cls, line, move, account, tx_currency, context):
where = (
(move.company == context.get('company'))
& (account.type != Null)
& (account.closed != Literal(True)))
if context.get('account'):
where &= line.account == context['account']
if context.get('account_from'):
where &= account.code >= context['account_from']
if context.get('account_to'):
where &= account.code <= context['account_to']
if context.get('currency'):
where &= tx_currency == context['currency']
if context.get('party'):
where &= line.party == context['party']
if context.get('document_number'):
value = '%%%s%%' % context['document_number']
where &= (
move.number.ilike(value)
| move.post_number.ilike(value)
| move.ext_ref.ilike(value))
return where
@classmethod
def _tables(cls):
pool = Pool()
Line = pool.get('account.move.line')
Move = pool.get('account.move')
Account = pool.get('account.account')
Company = pool.get('company.company')
line = Line.__table__()
move = Move.__table__()
account = Account.__table__()
company = Company.__table__()
return Line, line, move, account, company
@classmethod
def _lot_tables(cls, Line):
if 'lot' not in Line._fields:
return None, None
pool = Pool()
try:
Lot = pool.get('lot.lot')
Uom = pool.get('product.uom')
except KeyError:
return None, None
return Lot.__table__(), Uom.__table__()
@classmethod
def _account_label(cls, account):
return Concat(
Concat(Coalesce(account.code, ''), ' - '),
Coalesce(account.name, ''))
@classmethod
def _opening_balance_query(cls, context):
Line, line, move, account, company = cls._tables()
tx_currency = Coalesce(line.second_currency, company.currency)
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
tx_amount = Case(
(line.second_currency != Null,
Coalesce(line.amount_second_currency, 0)),
else_=base_amount)
with Transaction().set_context(
context, **cls._query_context('opening', context)):
line_query, _ = Line.query_get(line)
where = line_query & cls._extra_where(
line, move, account, tx_currency, context)
return (line.join(move, condition=line.move == move.id)
.join(account, condition=line.account == account.id)
.join(company, condition=move.company == company.id)
.select(
line.account.as_('account'),
tx_currency.as_('transaction_currency'),
Sum(base_amount).as_('base_amount'),
Sum(tx_amount).as_('transaction_amount'),
where=where,
group_by=[line.account, tx_currency],
having=Sum(base_amount) != 0))
@classmethod
def _line_query(cls, name, context):
Line, line, move, account, company = cls._tables()
lot, uom = cls._lot_tables(Line)
opening_balance = cls._opening_balance_query(context)
account_label = cls._account_label(account)
quantity = Literal(None)
unit = Literal(None)
tx_currency = Coalesce(line.second_currency, company.currency)
from_ = (line.join(move, condition=line.move == move.id)
.join(account, condition=line.account == account.id)
.join(company, condition=move.company == company.id))
from_ = from_.join(opening_balance, 'LEFT',
condition=(
(opening_balance.account == line.account)
& (opening_balance.transaction_currency == tx_currency)))
if lot is not None:
from_ = (from_
.join(lot, 'LEFT', condition=Column(line, 'lot') == lot.id)
.join(uom, 'LEFT', condition=lot.lot_unit_line == uom.id))
quantity = lot.lot_qt
unit = uom.symbol
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
second_amount = Coalesce(line.amount_second_currency, 0)
tx_amount = Case(
(line.second_currency != Null, second_amount),
else_=base_amount)
debit_tx = Case(
(line.second_currency == Null, Coalesce(line.debit, 0)),
(second_amount > 0, second_amount),
else_=Literal(0))
credit_tx = Case(
(line.second_currency == Null, Coalesce(line.credit, 0)),
(second_amount < 0, -second_amount),
else_=Literal(0))
with Transaction().set_context(
context, **cls._query_context(name, context)):
line_query, _ = Line.query_get(line)
where = line_query & cls._extra_where(
line, move, account, tx_currency, context)
window = Window(
[line.account, tx_currency],
order_by=[move.date.asc, move.number.asc, line.id.asc])
return from_.select(
(line.id * 3).as_('id'),
line.create_uid.as_('create_uid'),
line.create_date.as_('create_date'),
line.write_uid.as_('write_uid'),
line.write_date.as_('write_date'),
Literal('movement').as_('row_type'),
Literal(1).as_('row_sequence'),
move.company.as_('company'),
line.account.as_('account'),
account_label.as_('account_code'),
account.name.as_('account_name'),
tx_currency.as_('transaction_currency'),
company.currency.as_('base_currency'),
move.date.as_('posting_date'),
move.journal.as_('journal'),
move.number.as_('journal_entry_number'),
move.post_number.as_('voucher_number'),
move.date.as_('document_date'),
move.state.as_('posting_status'),
line.party.as_('party'),
line.move.as_('move'),
line.id.as_('move_line'),
move.origin.as_('origin'),
line.description.as_('description'),
move.description.as_('move_description'),
Coalesce(move.ext_ref, move.description).as_('reference'),
line.debit.as_('debit_base_currency'),
line.credit.as_('credit_base_currency'),
base_amount.as_('balance_base_currency'),
(Coalesce(opening_balance.base_amount, 0)
+ Sum(base_amount, window=window)).as_(
'running_balance_base_currency'),
debit_tx.as_('debit_transaction_currency'),
credit_tx.as_('credit_transaction_currency'),
tx_amount.as_('balance_transaction_currency'),
(Coalesce(opening_balance.transaction_amount, 0)
+ Sum(tx_amount, window=window)).as_(
'running_balance_transaction_currency'),
quantity.as_('payable_qty'),
unit.as_('uom'),
line.create_uid.as_('entered_by'),
line.create_date.as_('entered_date'),
line.write_uid.as_('modified_by'),
line.write_date.as_('modified_date'),
where=where)
@classmethod
def _summary_query(cls, name, context):
Line, line, move, account, company = cls._tables()
account_label = cls._account_label(account)
tx_currency = Coalesce(line.second_currency, company.currency)
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
tx_amount = Case(
(line.second_currency != Null,
Coalesce(line.amount_second_currency, 0)),
else_=base_amount)
row_sequence = 0 if name == 'opening' else 2
row_id = (
(Min(line.id) * 3 - 1) if name == 'opening'
else (Min(line.id) * 3 - 2))
with Transaction().set_context(
context, **cls._query_context(name, context)):
line_query, _ = Line.query_get(line)
where = line_query & cls._extra_where(
line, move, account, tx_currency, context)
return (line.join(move, condition=line.move == move.id)
.join(account, condition=line.account == account.id)
.join(company, condition=move.company == company.id)
.select(
row_id.as_('id'),
Literal(0).as_('create_uid'),
Max(line.create_date).as_('create_date'),
Literal(0).as_('write_uid'),
Max(line.write_date).as_('write_date'),
Literal(name).as_('row_type'),
Literal(row_sequence).as_('row_sequence'),
move.company.as_('company'),
line.account.as_('account'),
account_label.as_('account_code'),
account.name.as_('account_name'),
tx_currency.as_('transaction_currency'),
company.currency.as_('base_currency'),
Literal(None).as_('posting_date'),
Literal(None).as_('journal'),
Literal(None).as_('journal_entry_number'),
Literal(None).as_('voucher_number'),
Literal(None).as_('document_date'),
Literal(None).as_('posting_status'),
Literal(None).as_('party'),
Literal(None).as_('move'),
Literal(None).as_('move_line'),
Literal(None).as_('origin'),
Literal(name.title()).as_('description'),
Literal(None).as_('move_description'),
Literal(None).as_('reference'),
Literal(0).as_('debit_base_currency'),
Literal(0).as_('credit_base_currency'),
Sum(base_amount).as_('balance_base_currency'),
Sum(base_amount).as_('running_balance_base_currency'),
Literal(0).as_('debit_transaction_currency'),
Literal(0).as_('credit_transaction_currency'),
Sum(tx_amount).as_('balance_transaction_currency'),
Sum(tx_amount).as_('running_balance_transaction_currency'),
Literal(None).as_('payable_qty'),
Literal(None).as_('uom'),
Literal(None).as_('entered_by'),
Literal(None).as_('entered_date'),
Literal(None).as_('modified_by'),
Literal(None).as_('modified_date'),
where=where,
group_by=[
move.company, line.account, account.code, account.name,
tx_currency, company.currency],
having=Sum(base_amount) != 0))
@classmethod
def table_query(cls):
pool = Pool()
DetailContext = pool.get('account.general_ledger.detail.context')
context = DetailContext.get_context()
return Union(
cls._summary_query('opening', context),
cls._line_query('movement', context),
cls._summary_query('closing', context))
@classmethod
def get_origin(cls):
Move = Pool().get('account.move')
return Move.get_origin()
def _get_related_payment(self):
if not self.move_line:
return None
try:
Payment = Pool().get('account.payment')
except KeyError:
return None
payments = Payment.search([('line', '=', self.move_line.id)], limit=1)
return payments[0] if payments else None
@staticmethod
def _resolve_reference(value):
if not value or hasattr(value, '__name__'):
return value
if not isinstance(value, str):
return None
try:
model_name, record_id = value.split(',', 1)
record_id = int(record_id)
except ValueError:
return None
try:
Model = Pool().get(model_name)
except KeyError:
return None
return Model(record_id)
def get_document_field(self, name):
origin = self._resolve_reference(
self.origin or (self.move.origin if self.move else None))
if origin:
model_name = origin.__name__
if model_name == 'account.invoice':
if name == 'document_number':
return origin.number or origin.reference
side = {
'out': 'Customer',
'in': 'Supplier',
}.get(origin.type, '')
stage = ''
reference = (origin.reference or '').lower()
if 'provisional' in reference:
stage = 'Provisional'
elif 'final' in reference:
stage = 'Final'
return ' '.join(filter(None, ['Invoice', side, stage]))
if model_name == 'account.payment':
if name == 'document_number':
return origin.number
side = {
'receivable': 'Customer',
'payable': 'Supplier',
}.get(origin.kind, '')
return ' '.join(filter(None, ['Payment', side]))
if name == 'document_number':
return getattr(origin, 'number', None) or getattr(
origin, 'reference', None)
return getattr(origin, 'rec_name', None) or model_name
payment = self._get_related_payment()
if payment:
if name == 'document_number':
return payment.number
side = {
'receivable': 'Customer',
'payable': 'Supplier',
}.get(payment.kind, '')
return ' '.join(filter(None, ['Payment', side]))
return None
class GeneralLedger(Report):
__name__ = 'account.general_ledger'
@@ -2611,6 +3168,52 @@ class GeneralLedger(Report):
return report_context
class GeneralLedgerDetailReport(Report):
__name__ = 'account.general_ledger.detail.report'
@classmethod
def get_context(cls, records, header, data):
pool = Pool()
Company = pool.get('company.company')
Fiscalyear = pool.get('account.fiscalyear')
Period = pool.get('account.period')
context = Transaction().context
report_context = super().get_context(records, header, data)
report_context['company'] = Company(context['company'])
report_context['fiscalyear'] = Fiscalyear(context['fiscalyear'])
for period in ['start_period', 'end_period']:
if context.get(period):
report_context[period] = Period(context[period])
else:
report_context[period] = None
report_context['from_date'] = context.get('from_date')
report_context['to_date'] = context.get('to_date')
report_context['groups'] = cls._groups(records)
return report_context
@classmethod
def _groups(cls, records):
groups = []
current_key = None
current = None
for line in records:
currency = getattr(line, 'transaction_currency', None)
key = (
getattr(line, 'account_code', None),
getattr(currency, 'id', None))
if key != current_key:
current = {
'account': line.account_code or line.account_name or '',
'currency': currency,
'lines': [],
}
groups.append(current)
current_key = key
current['lines'].append(line)
return groups
class TrialBalance(Report):
__name__ = 'account.trial_balance'

View File

@@ -806,6 +806,70 @@ this repository contains the full copyright notices and license terms. -->
<field name="rule_group" ref="rule_group_general_ledger_line_companies"/>
</record>
<record model="ir.ui.view" id="general_ledger_detail_view_list">
<field name="model">account.general_ledger.detail</field>
<field name="type">tree</field>
<field name="name">general_ledger_detail_list</field>
</record>
<record model="ir.action.act_window" id="act_general_ledger_detail_form">
<field name="name">General Ledger Detail</field>
<field name="res_model">account.general_ledger.detail</field>
<field name="context_model">account.general_ledger.detail.context</field>
</record>
<record model="ir.action.act_window.view"
id="act_general_ledger_detail_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="general_ledger_detail_view_list"/>
<field name="act_window" ref="act_general_ledger_detail_form"/>
</record>
<record model="ir.action.report" id="report_general_ledger_detail">
<field name="name">General Ledger Detail</field>
<field name="records">listed</field>
<field name="model">account.general_ledger.detail</field>
<field name="report_name">account.general_ledger.detail.report</field>
<field name="report">account/general_ledger_detail.fodt</field>
</record>
<record model="ir.action.keyword" id="report_general_ledger_detail_keyword">
<field name="keyword">form_print</field>
<field name="model">account.general_ledger.detail,-1</field>
<field name="action" ref="report_general_ledger_detail"/>
</record>
<menuitem
name="General Ledger Detail"
parent="menu_reporting"
action="act_general_ledger_detail_form"
sequence="11"
id="menu_general_ledger_detail"/>
<record model="ir.model.access" id="access_general_ledger_detail">
<field name="model">account.general_ledger.detail</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_general_ledger_detail_account">
<field name="model">account.general_ledger.detail</field>
<field name="group" ref="group_account"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.rule.group" id="rule_group_general_ledger_detail_companies">
<field name="name">User in companies</field>
<field name="model">account.general_ledger.detail</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_general_ledger_detail_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_general_ledger_detail_companies"/>
</record>
<record model="ir.ui.view" id="general_ledger_account_context_view_form">
<field name="model">account.general_ledger.account.context</field>
<field name="type">form</field>
@@ -817,6 +881,11 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="general_ledger_account_context_view_form"/>
<field name="name">general_ledger_line_context_form</field>
</record>
<record model="ir.ui.view" id="general_ledger_detail_context_view_form">
<field name="model">account.general_ledger.detail.context</field>
<field name="type">form</field>
<field name="name">general_ledger_detail_context_form</field>
</record>
<record model="ir.ui.view" id="account_balance_sheet_view_tree">
<field name="model">account.account.type</field>

View File

@@ -0,0 +1,825 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:officeooo="http://openoffice.org/2009/office" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:generator>LibreOffice/7.3.5.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><meta:creation-date>2008-06-07T15:26:41</meta:creation-date><dc:date>2008-08-27T17:03:36</dc:date><meta:editing-cycles>1</meta:editing-cycles><meta:editing-duration>PT0S</meta:editing-duration><meta:document-statistic meta:table-count="4" meta:image-count="0" meta:object-count="0" meta:page-count="3" meta:paragraph-count="51" meta:word-count="145" meta:character-count="1808" meta:non-whitespace-character-count="1714"/><meta:user-defined meta:name="Info 1"/><meta:user-defined meta:name="Info 2"/><meta:user-defined meta:name="Info 3"/><meta:user-defined meta:name="Info 4"/></office:meta>
<office:settings>
<config:config-item-set config:name="ooo:view-settings">
<config:config-item config:name="ViewAreaTop" config:type="long">24130</config:config-item>
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaWidth" config:type="long">48129</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">22994</config:config-item>
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
<config:config-item-map-indexed config:name="Views">
<config:config-item-map-entry>
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">19627</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">29242</config:config-item>
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">24130</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">48128</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">47122</config:config-item>
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
<config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item>
<config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
<config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
<config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
</config:config-item-map-entry>
</config:config-item-map-indexed>
</config:config-item-set>
<config:config-item-set config:name="ooo:configuration-settings">
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item>
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
<config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
<config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
<config:config-item config:name="ApplyUserData" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintFaxName" config:type="string"/>
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
<config:config-item config:name="Rsid" config:type="int">4069242</config:config-item>
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
<config:config-item config:name="PrinterName" config:type="string"/>
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">2070049</config:config-item>
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">false</config:config-item>
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
<config:config-item config:name="TabOverflow" config:type="boolean">false</config:config-item>
</config:config-item-set>
</office:settings>
<office:scripts>
<office:script script:language="ooo:Basic">
<ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</office:script>
</office:scripts>
<office:font-face-decls>
<style:font-face style:name="Andale Sans UI" svg:font-family="&apos;Andale Sans UI&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="DejaVu Sans" svg:font-family="&apos;DejaVu Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif1" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Bold" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif2" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Regular" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>
<style:font-face style:name="Thorndale AMT" svg:font-family="&apos;Thorndale AMT&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#000000" draw:fill-color="#99ccff" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="lr-tb"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text">
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
<style:text-properties style:font-name="Liberation Serif2" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Regular" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="16pt" style:font-name-asian="DejaVu Sans" style:font-family-asian="&apos;DejaVu Sans&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="DejaVu Sans" style:font-family-complex="&apos;DejaVu Sans&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
</style:style>
<style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
<style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-size-complex="12pt" style:font-style-complex="italic"/>
</style:style>
<style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties fo:font-size="11pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-name="Liberation Serif1" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Bold" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="12pt" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Horizontal_20_Line" style:display-name="Horizontal Line" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="html">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" style:border-line-width-bottom="0.002cm 0.035cm 0.002cm" fo:padding="0cm" fo:border-left="none" fo:border-right="none" fo:border-top="none" fo:border-bottom="1.11pt double #808080" text:number-lines="false" text:line-number="0" style:join-border="false"/>
<style:text-properties fo:font-size="6pt" style:font-size-asian="6pt" style:font-size-complex="6pt"/>
</style:style>
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.5cm" style:type="center"/>
<style:tab-stop style:position="17cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
</style:style>
<style:style style:name="Header" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.795cm" style:type="center"/>
<style:tab-stop style:position="17.59cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.795cm" style:type="center"/>
<style:tab-stop style:position="17.59cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Text_20_body_20_indent" style:display-name="Text body indent" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-left="0.499cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
<style:paragraph-properties fo:margin-left="1cm" fo:margin-right="1cm" fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Title" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties fo:font-size="28pt" fo:font-weight="bold" style:font-size-asian="28pt" style:font-weight-asian="bold" style:font-size-complex="28pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Subtitle" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.106cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties fo:font-size="18pt" style:font-size-asian="18pt" style:font-size-complex="18pt"/>
</style:style>
<style:style style:name="Placeholder" style:family="text">
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
<style:text-properties style:font-name="StarSymbol" fo:font-family="StarSymbol" fo:font-size="9pt" style:font-name-asian="StarSymbol" style:font-family-asian="StarSymbol" style:font-size-asian="9pt" style:font-name-complex="StarSymbol" style:font-family-complex="StarSymbol" style:font-size-complex="9pt"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
</text:outline-style>
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
<text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
<style:style style:name="Default_20_Style.1" style:display-name="Default Style.1" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.2" style:display-name="Default Style.2" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.3" style:display-name="Default Style.3" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.4" style:display-name="Default Style.4" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.5" style:display-name="Default Style.5" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.6" style:display-name="Default Style.6" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.7" style:display-name="Default Style.7" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.8" style:display-name="Default Style.8" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.9" style:display-name="Default Style.9" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.10" style:display-name="Default Style.10" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.11" style:display-name="Default Style.11" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.12" style:display-name="Default Style.12" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.13" style:display-name="Default Style.13" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.14" style:display-name="Default Style.14" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.15" style:display-name="Default Style.15" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.16" style:display-name="Default Style.16" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<table:table-template table:name="Default Style" table:first-row-end-column="row" table:first-row-start-column="row" table:last-row-end-column="row" table:last-row-start-column="row">
<table:first-row table:style-name="Default_20_Style.1"/>
<table:last-row table:style-name="Default_20_Style.2"/>
<table:first-column table:style-name="Default_20_Style.3"/>
<table:last-column table:style-name="Default_20_Style.4"/>
<table:body table:style-name="Default_20_Style.9"/>
<table:even-rows table:style-name="Default_20_Style.5"/>
<table:odd-rows table:style-name="Default_20_Style.6"/>
<table:even-columns table:style-name="Default_20_Style.7"/>
<table:odd-columns table:style-name="Default_20_Style.8"/>
<table:background table:style-name="Default_20_Style.10"/>
<loext:first-row-even-column table:style-name="Default_20_Style.15"/>
<loext:last-row-even-column table:style-name="Default_20_Style.16"/>
<loext:first-row-end-column table:style-name="Default_20_Style.12"/>
<loext:first-row-start-column table:style-name="Default_20_Style.11"/>
<loext:last-row-end-column table:style-name="Default_20_Style.14"/>
<loext:last-row-start-column table:style-name="Default_20_Style.13"/>
</table:table-template>
</office:styles>
<office:automatic-styles>
<style:style style:name="Table2" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table2.A" style:family="table-column">
<style:table-column-properties style:column-width="7.98cm" style:rel-column-width="21845*"/>
</style:style>
<style:style style:name="Table2.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="none">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table1.A" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32768*"/>
</style:style>
<style:style style:name="Table1.B" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32767*"/>
</style:style>
<style:style style:name="Table1.A1" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border="none"/>
</style:style>
<style:style style:name="Table2" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table2.A" style:family="table-column">
<style:table-column-properties style:column-width="7.98cm" style:rel-column-width="21845*"/>
</style:style>
<style:style style:name="Table2.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="none">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table1.A" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32768*"/>
</style:style>
<style:style style:name="Table1.B" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32767*"/>
</style:style>
<style:style style:name="Table1.A1" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border="none"/>
</style:style>
<style:style style:name="Lines" style:family="table">
<style:table-properties style:width="23.94cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:break-before="auto" fo:break-after="auto" table:align="margins" fo:background-color="transparent" fo:keep-with-next="always" style:may-break-between-rows="true" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-properties>
</style:style>
<style:style style:name="Lines.A" style:family="table-column">
<style:table-column-properties style:column-width="2.499cm" style:rel-column-width="1417*"/>
</style:style>
<style:style style:name="Lines.B" style:family="table-column">
<style:table-column-properties style:column-width="3.358cm" style:rel-column-width="1904*"/>
</style:style>
<style:style style:name="Lines.C" style:family="table-column">
<style:table-column-properties style:column-width="3.36cm" style:rel-column-width="1905*"/>
</style:style>
<style:style style:name="Lines.F" style:family="table-column">
<style:table-column-properties style:column-width="4.002cm" style:rel-column-width="2269*"/>
</style:style>
<style:style style:name="Lines.1" style:family="table-row">
<style:table-row-properties fo:keep-together="always"/>
</style:style>
<style:style style:name="Lines.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A2" style:family="table-cell">
<style:table-cell-properties fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A3" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.G3" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A4" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.C4" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.G4" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.A6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.B6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.C6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.D6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.E6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.F6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.G6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.A7" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.A8" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3" style:family="table" style:master-page-name="">
<style:table-properties style:width="23.94cm" style:page-number="auto" fo:break-before="auto" fo:break-after="auto" table:align="margins" fo:background-color="transparent" style:shadow="none" fo:keep-with-next="always" style:may-break-between-rows="true" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-properties>
</style:style>
<style:style style:name="Table3.A" style:family="table-column">
<style:table-column-properties style:column-width="5.851cm" style:rel-column-width="3317*"/>
</style:style>
<style:style style:name="Table3.B" style:family="table-column">
<style:table-column-properties style:column-width="3.362cm" style:rel-column-width="1906*"/>
</style:style>
<style:style style:name="Table3.E" style:family="table-column">
<style:table-column-properties style:column-width="4.001cm" style:rel-column-width="2268*"/>
</style:style>
<style:style style:name="Table3.1" style:family="table-row">
<style:table-row-properties fo:background-color="transparent" fo:keep-together="always">
<style:background-image/>
</style:table-row-properties>
</style:style>
<style:style style:name="Table3.A1" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3.A2" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3.A3" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3.F3" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P2" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P3" style:family="paragraph" style:parent-style-name="Footer">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P4" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P5" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:keep-together="always" style:shadow="none" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P6" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P7" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" style:shadow="none" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="12pt" fo:font-weight="bold" style:font-size-asian="12pt" style:font-weight-asian="bold" style:font-size-complex="12pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P8" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-size="12pt" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="10.5pt" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-weight-complex="normal"/>
</style:style>
<style:style style:name="P9" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P10" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P11" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties officeooo:paragraph-rsid="002aca3c" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P12" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P13" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="11pt" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
</style:style>
<style:style style:name="P14" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="11pt" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
</style:style>
<style:style style:name="P15" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="11pt" style:text-underline-style="none" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
</style:style>
<style:style style:name="P16" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P17" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="00251ac8"/>
</style:style>
<style:style style:name="P18" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P19" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="00251ac8" fo:background-color="transparent"/>
</style:style>
<style:style style:name="P20" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:break-before="page"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:paragraph-rsid="0037c304"/>
</style:style>
<style:style style:name="P23" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:paragraph-rsid="003a045d"/>
</style:style>
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:rsid="003955ec" officeooo:paragraph-rsid="003a045d"/>
</style:style>
<style:style style:name="P25" style:family="paragraph" style:parent-style-name="Heading_20_1">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:font-name="Liberation Serif1" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
</style:style>
<style:style style:name="P28" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:color="#000000" loext:opacity="100%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Serif" fo:font-size="12pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="12pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-overline-style="none" style:text-overline-color="font-color"/>
</style:style>
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties officeooo:paragraph-rsid="002aca3c" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P31" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:break-before="page" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
</style:style>
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
</style:style>
<style:style style:name="P38" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="12pt" style:text-underline-style="none" fo:font-weight="normal" officeooo:paragraph-rsid="003e177a" fo:background-color="transparent" style:font-size-asian="10.5pt" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-weight-complex="normal"/>
</style:style>
<style:style style:name="T1" style:family="text">
<style:text-properties officeooo:rsid="0037c304"/>
</style:style>
<style:style style:name="T2" style:family="text">
<style:text-properties officeooo:rsid="0039b016"/>
</style:style>
<style:style style:name="T3" style:family="text">
<style:text-properties officeooo:rsid="003e177a"/>
</style:style>
<style:style style:name="T4" style:family="text">
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="T5" style:family="text">
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" officeooo:rsid="003e177a"/>
</style:style>
<style:style style:name="T6" style:family="text">
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="T7" style:family="text">
<style:text-properties style:text-underline-style="none" officeooo:rsid="003e177a"/>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="27.94cm" fo:page-height="21.59cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.55cm" style:layout-grid-ruby-height="0cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
<style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="none" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style>
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-bottom="0.499cm"/>
</style:header-style>
<style:footer-style>
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0.499cm"/>
</style:footer-style>
</style:page-layout>
<style:style style:name="dp1" style:family="drawing-page">
<style:drawing-page-properties draw:background-size="full"/>
</style:style>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1">
<style:header>
<table:table table:name="Table2" table:style-name="Table2">
<table:table-column table:style-name="Table2.A" table:number-columns-repeated="3"/>
<table:table-row table:style-name="TableLine94368948362784">
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
<text:p text:style-name="Header">Company: <text:placeholder text:placeholder-type="text">&lt;company.rec_name&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
<text:p text:style-name="P27">General Ledger Detail<text:placeholder text:placeholder-type="text">&lt;if test=&quot;not context.get(&apos;posted&apos;)&quot;&gt;</text:placeholder> <text:span text:style-name="T3">Draft</text:span><text:span text:style-name="T3"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:span></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
<text:p text:style-name="P2">Print Date: <text:placeholder text:placeholder-type="text">&lt;format_date(datetime.date.today(), user.language)&gt;</text:placeholder><text:s/>at <text:placeholder text:placeholder-type="text">&lt;datetime.datetime.now().strftime(&apos;%H:%M:%S&apos;)&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
</table:table>
</style:header>
<style:footer>
<table:table table:name="Table1" table:style-name="Table1">
<table:table-column table:style-name="Table1.A"/>
<table:table-column table:style-name="Table1.B"/>
<table:table-row table:style-name="TableLine94368949141280">
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="Footer">User: <text:placeholder text:placeholder-type="text">&lt;user.rec_name&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="P3"><text:page-number text:select-page="current">1</text:page-number>/<text:page-count>3</text:page-count></text:p>
</table:table-cell>
</table:table-row>
</table:table>
</style:footer>
</style:master-page>
</office:master-styles>
<office:body>
<office:text text:use-soft-page-breaks="true">
<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
</text:sequence-decls>
<text:p text:style-name="P25">General Ledger Detail<text:placeholder text:placeholder-type="text">&lt;if test=&quot;not context.get(&apos;posted&apos;)&quot;&gt;</text:placeholder> <text:span text:style-name="T3">Draft</text:span><text:span text:style-name="T3"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:span></text:p>
<text:p text:style-name="P23"><text:span text:style-name="T1">Fiscal Year: </text:span><text:placeholder text:placeholder-type="text">&lt;fiscalyear.rec_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;start_period or end_period&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:span text:style-name="T1">From Period</text:span>: <text:placeholder text:placeholder-type="text">&lt;start_period.name if start_period else &apos;&apos;&gt;</text:placeholder><text:s/><text:span text:style-name="T2">To</text:span> <text:placeholder text:placeholder-type="text">&lt;end_period.name if end_period else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;from_date or to_date&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P24">From Date: <text:placeholder text:placeholder-type="text">&lt;format_date(from_date, user.language) if from_date else &apos;&apos;&gt;</text:placeholder><text:s/>To <text:placeholder text:placeholder-type="text">&lt;format_date(to_date, user.language) if to_date else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;group in groups&quot;&gt;</text:placeholder></text:p>
<table:table table:name="Lines" table:style-name="Lines">
<table:table-column table:style-name="Lines.A"/>
<table:table-column table:style-name="Lines.B"/>
<table:table-column table:style-name="Lines.C"/>
<table:table-column table:style-name="Lines.B" table:number-columns-repeated="2"/>
<table:table-column table:style-name="Lines.F" table:number-columns-repeated="2"/>
<table:table-header-rows>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A1" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P5">Account / Currency</text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A2" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P7"><text:placeholder text:placeholder-type="text">&lt;group[&apos;account&apos;]&gt;</text:placeholder><text:s/>/<text:s/><text:placeholder text:placeholder-type="text">&lt;group[&apos;currency&apos;].rec_name if group[&apos;currency&apos;] else &apos;&apos;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Date</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Journal</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Entry / Doc.</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="Table_20_Heading">Debit</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Credit</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Running</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.G3" office:value-type="string"><text:p text:style-name="P6">Description / Ref.</text:p></table:table-cell>
</table:table-row>
</table:table-header-rows>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A5" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in group[&apos;lines&apos;]&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/>
</table:table-row>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A6" office:value-type="string"><text:p text:style-name="P33"><text:placeholder text:placeholder-type="text">&lt;format_date(line.posting_date, user.language) if line.posting_date else &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.B6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;line.journal.rec_name if line.journal else &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.C6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;line.journal_entry_number or &apos;&apos;&gt;</text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text">&lt;line.document_number or line.voucher_number or &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.D6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.debit_base_currency or 0, user.language, line.base_currency or company.currency)&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.E6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.credit_base_currency or 0, user.language, line.base_currency or company.currency)&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.F6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.running_balance_base_currency or 0, user.language, line.base_currency or company.currency)&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.G6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;line.description or line.move_description or &apos;&apos;&gt;</text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text">&lt;line.party.rec_name if line.party else &apos;&apos;&gt;</text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text">&lt;line.reference or &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
</table:table-row>
<text:soft-page-break/>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A7" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/>
</table:table-row>
</table:table>
<text:p text:style-name="P8"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</office:text>
</office:body>
</office:document>

View File

@@ -1013,7 +1013,9 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
journal = fields.Function(fields.Many2One('account.journal',"Journal"),'get_journal')
rate = fields.Function(fields.Numeric("Rate",digits=(1,6)),'get_rate')
rate = fields.Numeric("Rate", digits=(10, 6), states={
'readonly': _states['readonly'],
})
del _states
@@ -1117,46 +1119,84 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
return line.move.id
def get_rate(self,name=None):
if self.amount_second_currency:
return round((self.credit if self.credit else self.debit) / abs(self.amount_second_currency),6)
@fields.depends(
'debit', 'credit',
'date','second_currency','amount_second_currency')
def on_change_amount_second_currency(self):
Currency = Pool().get('currency.currency')
Date = Pool().get('ir.date')
if self.second_currency != None and self.amount_second_currency != None and (not self.credit and not self.debit):
tdate = Date.today()
if self.date:
tdate = self.date
rate = Currency._get_rate([self.second_currency],tdate)
if rate:
rate = rate[self.second_currency.id]
if self.amount_second_currency > 0:
self.debit = round(rate * self.amount_second_currency,2)
elif self.amount_second_currency > 0:
self.credit = round(rate * abs(self.amount_second_currency),2)
self.rate = self.get_rate()
amount = self.credit if self.credit else self.debit
amount_second_currency = getattr(self, 'amount_second_currency', None)
if amount_second_currency and amount:
return round(abs(amount_second_currency) / amount, 6)
def _set_rate_from_amounts(self):
if (getattr(self, 'amount_second_currency', None)
and not getattr(self, 'rate', None)):
self.rate = self.get_rate()
@fields.depends(
'debit', 'credit',
'date','second_currency','amount_second_currency')
'date','second_currency','amount_second_currency', 'rate',
'origin', 'move_origin', 'move', '_parent_move.origin',
'company', 'account')
def on_change_second_currency(self):
if self._manual_rate_mode():
self.rate = self._get_second_currency_rate()
self._compute_amount_from_second_currency()
@fields.depends(
'debit', 'credit',
'date','second_currency','amount_second_currency', 'rate',
'origin', 'move_origin', 'move', '_parent_move.origin',
'company', 'account')
def on_change_rate(self):
if self._manual_rate_mode() and not self.debit and not self.credit:
self._compute_amount_from_second_currency()
def _manual_rate_mode(self):
if getattr(self, 'origin', None) or getattr(self, 'move_origin', None):
return False
move = getattr(self, 'move', None)
if move and getattr(move, 'origin', None):
return False
return True
def _get_second_currency_rate(self):
Currency = Pool().get('currency.currency')
Date = Pool().get('ir.date')
if self.second_currency != None and self.amount_second_currency != None and (not self.credit and not self.debit):
tdate = Date.today()
if self.date:
tdate = self.date
rate = Currency._get_rate([self.second_currency],tdate)
if rate:
rate = rate[self.second_currency.id]
if self.second_currency is None:
return None
tdate = Date.today()
if self.date:
tdate = self.date
rates = Currency._get_rate([self.second_currency], tdate)
return rates.get(self.second_currency.id) if rates else None
def _get_company_currency(self):
Company = Pool().get('company.company')
for record in (self, getattr(self, 'move', None),
getattr(self, 'account', None)):
company = getattr(record, 'company', None)
if company:
return company.currency
company_id = Transaction().context.get('company')
if company_id:
return Company(company_id).currency
def _compute_amount_from_second_currency(self):
if self.second_currency is not None and self.amount_second_currency is not None:
if not self.rate:
self.rate = self._get_second_currency_rate()
if self.rate:
company_currency = self._get_company_currency()
if not company_currency:
return
amount = company_currency.round(
abs(self.amount_second_currency) / self.rate)
if self.amount_second_currency > 0:
self.debit = round(rate * self.amount_second_currency,2)
elif self.amount_second_currency > 0:
self.credit = round(rate * abs(self.amount_second_currency),2)
self.rate = self.get_rate()
self.debit = amount
self.credit = Decimal(0)
elif self.amount_second_currency < 0:
self.debit = Decimal(0)
self.credit = amount
else:
self.debit = Decimal(0)
self.credit = Decimal(0)
@fields.depends(
'move', 'debit', 'credit',
@@ -1223,15 +1263,26 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
if self.debit:
self.credit = Decimal(0)
self._amount_second_currency_sign()
self.rate = self.get_rate()
@fields.depends('debit', 'credit', 'amount_second_currency')
def on_change_credit(self):
if self.credit:
self.debit = Decimal(0)
self._amount_second_currency_sign()
self.rate = self.get_rate()
@fields.depends('amount_second_currency', 'debit', 'credit')
@fields.depends(
'amount_second_currency', 'debit', 'credit', 'date', 'second_currency',
'rate', 'origin', 'move_origin', 'move', '_parent_move.origin',
'company', 'account')
def on_change_amount_second_currency(self):
if self._manual_rate_mode():
if not self.rate:
self.rate = self._get_second_currency_rate()
self._compute_amount_from_second_currency()
elif not self.rate:
self.rate = self.get_rate()
self._amount_second_currency_sign()
def _amount_second_currency_sign(self):
@@ -1241,6 +1292,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
self.amount_second_currency = \
self.amount_second_currency.copy_sign(
(self.debit or 0) - (self.credit or 0))
self._set_rate_from_amounts()
@fields.depends('account')
def on_change_account(self):
@@ -1545,6 +1597,14 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
for fname in move_fields(move_name=False):
vals.setdefault(fname, None)
lines = super(Line, cls).create(vlist)
to_save = []
for line in lines:
if (getattr(line, 'amount_second_currency', None)
and not getattr(line, 'rate', None)):
line._set_rate_from_amounts()
to_save.append(line)
if to_save:
cls.save(to_save)
period_and_journals = set((line.period, line.journal)
for line in lines)
for period, journal in period_and_journals:

View File

@@ -12,7 +12,7 @@ from trytond.modules.account.exceptions import (
from trytond.modules.account.tax import TaxableMixin
from trytond.modules.company.tests import (
CompanyTestMixin, PartyCompanyCheckEraseMixin, create_company, set_company)
from trytond.modules.currency.tests import create_currency
from trytond.modules.currency.tests import add_currency_rate, create_currency
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.transaction import Transaction, inactive_records
@@ -576,6 +576,54 @@ class AccountTestCase(
self.assertEqual(
cash_cur.amount_second_currency, Decimal(50))
@with_transaction()
def test_move_line_second_currency_amount_on_change(self):
'Test account move line computes amount from second currency'
pool = Pool()
Account = pool.get('account.account')
Line = pool.get('account.move.line')
company = create_company()
with set_company(company):
create_chart(company)
second_currency = create_currency('sec')
add_currency_rate(second_currency, Decimal('0.939506'))
expense, = Account.search([
('type.expense', '=', True),
])
line = Line(
account=expense,
second_currency=second_currency,
amount_second_currency=Decimal('100.00'))
line.on_change_amount_second_currency()
self.assertEqual(line.debit, Decimal('106.44'))
self.assertEqual(line.credit, Decimal(0))
self.assertEqual(line.rate, Decimal('0.939506'))
self.assertEqual(line.get_rate(), Decimal('0.939496'))
line = Line(
account=expense,
credit=Decimal('7.61'),
second_currency=second_currency,
amount_second_currency=Decimal('-108.10'))
line.on_change_amount_second_currency()
self.assertEqual(line.debit, Decimal(0))
self.assertEqual(line.credit, Decimal('115.06'))
self.assertEqual(line.rate, Decimal('0.939506'))
self.assertEqual(line.get_rate(), Decimal('0.939510'))
line.debit = line.credit = Decimal(0)
line.rate = Decimal('1.100000')
line.on_change_rate()
self.assertEqual(line.debit, Decimal(0))
self.assertEqual(line.credit, Decimal('98.27'))
self.assertEqual(line.rate, Decimal('1.100000'))
@with_transaction()
def test_account_type_amount(self):
"Test account type amount"
@@ -2049,5 +2097,121 @@ class AccountTestCase(
self.assertEqual(account.parent, new_account)
@with_transaction()
def test_general_ledger_detail_models_are_registered(self):
"Test General Ledger Detail models are registered with expected fields"
pool = Pool()
Detail = pool.get('account.general_ledger.detail')
DetailContext = pool.get('account.general_ledger.detail.context')
DetailReport = pool.get(
'account.general_ledger.detail.report', type='report')
for name in [
'row_type', 'account_code', 'transaction_currency',
'journal_entry_number', 'document_number',
'debit_base_currency', 'credit_base_currency',
'running_balance_base_currency',
'debit_transaction_currency',
'credit_transaction_currency',
'running_balance_transaction_currency', 'payable_qty']:
self.assertIn(name, Detail._fields)
for name in [
'debit_base_currency', 'credit_base_currency',
'balance_base_currency', 'running_balance_base_currency',
'debit_transaction_currency',
'credit_transaction_currency',
'balance_transaction_currency',
'running_balance_transaction_currency']:
self.assertEqual(Detail._fields[name].digits, (16, 2))
self.assertEqual(Detail._fields['payable_qty'].digits, (16, 5))
for name in [
'account', 'account_from', 'account_to', 'currency', 'party',
'document_number']:
self.assertIn(name, DetailContext._fields)
account = pool.get('account.account').__table__()
account_label = Detail._account_label(account)
self.assertIn('||', str(account_label))
self.assertNotIn(' + ', str(account_label))
self.assertEqual(account_label.params, ('', ' - ', ''))
self.assertEqual(
DetailReport.__name__, 'account.general_ledger.detail.report')
@with_transaction()
def test_general_ledger_detail_report_groups_by_account_currency(self):
"Test General Ledger Detail report groups lines by account and currency"
DetailReport = Pool().get(
'account.general_ledger.detail.report', type='report')
currency = type('Currency', (), {'id': 1})()
other_currency = type('Currency', (), {'id': 2})()
first = type('Line', (), {
'account_code': '1000 - Cash',
'account_name': 'Cash',
'transaction_currency': currency,
})()
second = type('Line', (), {
'account_code': '1000 - Cash',
'account_name': 'Cash',
'transaction_currency': currency,
})()
third = type('Line', (), {
'account_code': '2000 - Revenue',
'account_name': 'Revenue',
'transaction_currency': other_currency,
})()
groups = DetailReport._groups([first, second, third])
self.assertEqual(len(groups), 2)
self.assertEqual(groups[0]['account'], '1000 - Cash')
self.assertEqual(groups[0]['currency'], currency)
self.assertEqual(groups[0]['lines'], [first, second])
self.assertEqual(groups[1]['account'], '2000 - Revenue')
self.assertEqual(groups[1]['currency'], other_currency)
self.assertEqual(groups[1]['lines'], [third])
@with_transaction()
def test_general_ledger_detail_date_contexts(self):
"Test General Ledger Detail opening and movement date contexts"
Detail = Pool().get('account.general_ledger.detail')
context = {
'from_date': datetime.date(2026, 1, 10),
'to_date': datetime.date(2026, 1, 31),
}
self.assertEqual(Detail._query_context('opening', context), {
'periods': None,
'from_date': None,
'to_date': datetime.date(2026, 1, 9),
})
self.assertEqual(Detail._query_context('movement', context), {
'periods': None,
'from_date': datetime.date(2026, 1, 10),
'to_date': datetime.date(2026, 1, 31),
})
self.assertEqual(Detail._query_context('closing', context), {
'periods': None,
'from_date': None,
'to_date': datetime.date(2026, 1, 31),
})
@with_transaction()
def test_general_ledger_detail_document_field_reference_string(self):
"Test General Ledger Detail document fields resolve reference strings"
pool = Pool()
Detail = pool.get('account.general_ledger.detail')
Party = pool.get('party.party')
party = Party(name='Customer')
party.save()
detail = Detail()
detail.origin = '%s,%s' % (Party.__name__, party.id)
self.assertIsNone(detail.get_document_field('document_number'))
self.assertEqual(detail.get_document_field('document_type'),
party.rec_name)
del ModuleTestCase

View File

@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="fiscalyear"/>
<field name="fiscalyear"/>
<label name="company"/>
<field name="company"/>
<label name="journal"/>
<field name="journal" widget="selection"/>
<group col="-1" colspan="2" id="checkboxes">
<label name="posted"/>
<field name="posted"/>
</group>
<label name="start_period"/>
<field name="start_period"/>
<label name="end_period"/>
<field name="end_period"/>
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
<label name="account"/>
<field name="account"/>
<label name="currency"/>
<field name="currency"/>
<label name="account_from"/>
<field name="account_from"/>
<label name="account_to"/>
<field name="account_to"/>
<label name="party"/>
<field name="party"/>
<label name="document_number"/>
<field name="document_number"/>
</form>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="row_type"/>
<field name="account_code" expand="1"/>
<field name="transaction_currency"/>
<field name="base_currency" optional="1"/>
<field name="posting_date"/>
<field name="journal"/>
<field name="journal_entry_number"/>
<field name="document_number"/>
<field name="voucher_number" optional="1"/>
<field name="document_type" optional="1"/>
<field name="document_date" optional="1"/>
<field name="posting_status" optional="1"/>
<field name="party" optional="1"/>
<field name="description" expand="1" optional="1"/>
<field name="move_description" expand="1" optional="1"/>
<field name="reference" optional="1"/>
<field name="origin" optional="1"/>
<field name="debit_base_currency" sum="1"/>
<field name="credit_base_currency" sum="1"/>
<field name="balance_base_currency" sum="1"/>
<field name="running_balance_base_currency" optional="1"/>
<field name="debit_transaction_currency" sum="1" optional="1"/>
<field name="credit_transaction_currency" sum="1" optional="1"/>
<field name="balance_transaction_currency" sum="1" optional="1"/>
<field name="running_balance_transaction_currency" optional="1"/>
<field name="payable_qty" sum="1" optional="1"/>
<field name="uom" optional="1"/>
<field name="entered_by" optional="1"/>
<field name="entered_date" optional="1"/>
<field name="modified_by" optional="1"/>
<field name="modified_date" optional="1"/>
<field name="move" optional="1"/>
<field name="move_line" optional="1"/>
<field name="company" optional="1"/>
</tree>

View File

@@ -26,6 +26,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="amount_second_currency" symbol=""/>
<label name="second_currency"/>
<field name="second_currency"/>
<label name="rate"/>
<field name="rate"/>
</page>
<page name="tax_lines">
<field name="tax_lines" colspan="4"/>

View File

@@ -0,0 +1,15 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from . import account
def register():
Pool.register(
account.AccountTemplate,
module='account_ch_os', type_='model')
Pool.register(
account.CreateChart,
module='account_ch_os', type_='wizard')

View File

@@ -0,0 +1,27 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
class AccountTemplate(metaclass=PoolMeta):
__name__ = 'account.account.template'
class CreateChart(metaclass=PoolMeta):
__name__ = 'account.create_chart'
def default_properties(self, fields):
pool = Pool()
ModelData = pool.get('ir.model.data')
defaults = super().default_properties(fields)
try:
template_id = ModelData.get_id('account_ch_os', 'root')
except KeyError:
return defaults
if self.account.account_template.id == template_id:
defaults['account_receivable'] = self.get_account(
'account_ch_os.acct_110000')
defaults['account_payable'] = self.get_account(
'account_ch_os.acct_200000')
return defaults

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,12 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.tests.test_tryton import ModuleTestCase
class AccountCHOSTestCase(ModuleTestCase):
'Test Account CH OS module'
module = 'account_ch_os'
del ModuleTestCase

View File

@@ -0,0 +1,6 @@
[tryton]
version=7.2.3
depends:
account
xml:
account_ch_os.xml

File diff suppressed because it is too large Load Diff

View File

@@ -485,7 +485,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
})
cls.__rpc__.update({
'post': RPC(
readonly=False, instantiate=0, fresh_session=True),
readonly=False, instantiate=0, fresh_session=False),
})
@classmethod
@@ -515,7 +515,9 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
~((table.state == 'cancelled') & (table.number == Null)),
CharLength(table.number), table.number]
@fields.depends('selection_rate','rate_date')
@fields.depends(
'selection_rate', 'rate_date', 'currency', 'company', 'invoice_date',
'lines')
def on_change_with_rate(self, name=None):
return self.get_selected_rate()
@@ -528,8 +530,8 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
def get_selected_rate(self,name=None):
Currency = Pool().get('currency.currency')
Date = Pool().get('ir.date')
company = self.company
currency = self.currency
company = getattr(self, 'company', None)
currency = getattr(self, 'currency', None)
if not currency or not company:
return None
@@ -563,6 +565,16 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
if rate:
return round(1/rate,6) or 1
def _compute_company_currency_amount(self, amount, date=None):
Currency = Pool().get('currency.currency')
if self.currency == self.company.currency:
return amount
if self.rate:
return self.company.currency.round(amount / self.rate)
with Transaction().set_context(date=date or self.currency_date):
return Currency.compute(
self.currency, amount, self.company.currency)
@staticmethod
def default_type():
return Transaction().context.get('type', 'out')
@@ -1218,6 +1230,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.rate = line.get_rate()
line.account = self.account
logger.info("_GET_MOVE_LINE_ACCOUNT:%s",line.account)
if self.account.party_required:
@@ -1286,14 +1299,16 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
remainder = sum(l.debit - l.credit for l in move_lines)
if self.payment_term:
payment_date = self.payment_term_date or self.invoice_date or today
model = str(self.lines[0].origin).split(",")[0] if self.lines[0].origin else None
origin = self.lines[0].origin if self.lines and self.lines[0].origin else None
model = str(origin).split(",")[0] if origin else None
logger.info("MODEL:%s",model)
line = None
if model:
Line = Pool().get(model)
line = Line(int(str(self.lines[0].origin).split(",")[1]))
line = Line(int(str(origin).split(",")[1]))
logger.info("LINE:%s",line)
term_lines = self.payment_term.compute(
self.total_amount, self.currency, payment_date, line)
term_lines = self.payment_term.compute(
self.total_amount, self.currency, payment_date, line)
else:
term_lines = [(self.payment_term_date or today, self.total_amount)]
past_payment_term_dates = []
@@ -1890,17 +1905,17 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
cls._check_taxes(invoices)
# cls._check_similar(invoices)
invoices_in = cls.browse([i for i in invoices if i.type == 'in'])
cls.set_number(invoices_in)
cls.set_number(invoices)
cls._store_cache(invoices)
moves = []
for invoice in invoices:
if invoice.type == 'in':
move = invoice.get_move()
if move != invoice.move:
invoice.move = move
moves.append(move)
if invoice.currency != invoice.company.currency and not invoice.rate:
invoice.rate = invoice.get_selected_rate()
move = invoice.get_move()
if move != invoice.move:
invoice.move = move
moves.append(move)
invoice.do_lot_invoicing()
if moves:
Move.save(moves)
@@ -1963,23 +1978,35 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
move_line_.amount_second_currency = -original_amount
if amount < 0:
lot_has_sale_invoice = (
move_line.lot
and (move_line.lot.sale_invoice_line
or move_line.lot.sale_invoice_line_prov))
move_line.debit = Decimal(0)
move_line.credit = -amount
move_line.account = gl.product.account_stock_used if not (move_line.lot.sale_invoice_line or move_line.lot.sale_invoice_line_prov) else gl.product.account_stock_out_used
move_line.account = gl.product.account_stock_used if not lot_has_sale_invoice else gl.product.account_stock_out_used
move_line.account = gl.product.account_cogs_used if gl.fee else move_line.account
move_line_.credit = Decimal(0)
move_line_.debit = -amount
move_line_.account = gl.product.account_stock_in_used
else:
lot_has_sale_invoice = (
move_line.lot
and (move_line.lot.sale_invoice_line
or move_line.lot.sale_invoice_line_prov))
move_line.debit = amount
move_line.credit = Decimal(0)
move_line.account = gl.product.account_stock_used if not (move_line.lot.sale_invoice_line or move_line.lot.sale_invoice_line_prov) else gl.product.account_stock_out_used
move_line.account = gl.product.account_stock_used if not lot_has_sale_invoice else gl.product.account_stock_out_used
move_line.account = gl.product.account_cogs_used if gl.fee else move_line.account
move_line_.debit = Decimal(0)
move_line_.credit = amount
move_line_.account = gl.product.account_stock_in_used
move_lines = [move_line,move_line_]
if getattr(move_line, 'amount_second_currency', None):
move_line.rate = move_line.get_rate()
if getattr(move_line_, 'amount_second_currency', None):
move_line_.rate = move_line_.get_rate()
if drop:
drop_line = AccountMoveLine()
@@ -2017,6 +2044,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
drop_line_.second_currency = self.currency
drop_line_.amount_second_currency = -original_amount
move_lines = [drop_line,drop_line_]
if getattr(drop_line, 'amount_second_currency', None):
drop_line.rate = drop_line.get_rate()
if getattr(drop_line_, 'amount_second_currency', None):
drop_line_.rate = drop_line_.get_rate()
return move_lines
@@ -2038,10 +2069,16 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
var_qt = sum([i.quantity for i in gl])
logger.info("LOT_TO_PROCESS:%s",lot)
logger.info("FEE_TO_PROCESS:%s",gl[0].fee)
if (gl[0].fee and not gl[0].product.landed_cost):
fee_correction = (
gl[0].fee
and any(Decimal(str(i.quantity or 0)) < 0 for i in gl)
and any(Decimal(str(i.quantity or 0)) > 0 for i in gl))
if (gl[0].fee and not gl[0].product.landed_cost
and not fee_correction):
diff = gl[0].fee.amount - gl[0].fee.get_non_cog(lot)
account_move = gl[0].fee._get_account_move_fee(lot,'in',diff)
Move.save([account_move])
if account_move:
Move.save([account_move])
if (lot and not gl[0].fee) or (gl[0].fee and gl[0].product.landed_cost):
adjust_move_lines = []
mov = None
@@ -3174,7 +3211,6 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
def _compute_taxes(self):
pool = Pool()
Currency = pool.get('currency.currency')
TaxLine = pool.get('account.tax.line')
tax_lines = []
@@ -3183,11 +3219,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
taxes = self._get_taxes().values()
for tax in taxes:
amount = tax['base']
with Transaction().set_context(
date=self.invoice.currency_date):
amount = Currency.compute(
self.invoice.currency, amount,
self.invoice.company.currency)
amount = self.invoice._compute_company_currency_amount(amount)
tax_line = TaxLine()
tax_line.amount = amount
tax_line.type = 'base'
@@ -3252,6 +3284,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.rate = line.get_rate()
line.account = self.account
logger.info("GET_MOVE_LINES_ACCOUNT:%s",line.account)
if self.account.party_required:
@@ -3511,9 +3544,7 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
'''
Return a list of move lines instances for invoice tax
'''
Currency = Pool().get('currency.currency')
pool = Pool()
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
TaxLine = pool.get('account.tax.line')
line = MoveLine()
@@ -3521,11 +3552,8 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
return []
line.description = self.description
if self.invoice.currency != self.invoice.company.currency:
with Transaction().set_context(date=self.invoice.currency_date):
amount = Currency.compute(self.invoice.currency, self.amount,
self.invoice.company.currency)
base = Currency.compute(self.invoice.currency, self.base,
self.invoice.company.currency)
amount = self.invoice._compute_company_currency_amount(self.amount)
base = self.invoice._compute_company_currency_amount(self.base)
line.amount_second_currency = self.amount
line.second_currency = self.invoice.currency
else:
@@ -3547,6 +3575,7 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.rate = line.get_rate()
line.account = self.account
if self.account.party_required:
line.party = self.invoice.party

View File

@@ -288,7 +288,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.action.report" id="report_invoice">
<field name="name">Provisional Invoice</field>
<field name="name">Invoice</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/invoice.fodt</field>
@@ -314,7 +314,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.action.report" id="report_invoice_ict_final">
<field name="name">Final Invoice</field>
<field name="name">CN/DN</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/invoice_ict_final.fodt</field>
@@ -326,6 +326,45 @@ this repository contains the full copyright notices and license terms. -->
<field name="action" ref="report_invoice_ict_final"/>
</record>
<record model="ir.action.report" id="report_invoice_ict_commission_final">
<field name="name">CN/DN Commission</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/commission__ict_final.fodt</field>
<field name="single" eval="True"/>
</record>
<record model="ir.action.keyword" id="report_invoice_ict_commission_final_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="report_invoice_ict_commission_final"/>
</record>
<record model="ir.action.report" id="report_sale_commission_ict">
<field name="name">Commission invoice Sale</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/sale_commission_ict.fodt</field>
<field name="single" eval="True"/>
</record>
<record model="ir.action.keyword" id="report_sale_commission_ict_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="report_sale_commission_ict"/>
</record>
<record model="ir.action.report" id="report_purchase_commission_ict">
<field name="name">Commission invoice Purchase</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/purchase_commission_ict.fodt</field>
<field name="single" eval="True"/>
</record>
<record model="ir.action.keyword" id="report_purchase_commission_ict_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="report_purchase_commission_ict"/>
</record>
<record model="ir.sequence.type" id="sequence_type_account_invoice">
<field name="name">Invoice</field>
</record>

View File

@@ -2,19 +2,19 @@
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:officeooo="http://openoffice.org/2009/office" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta>
<dc:title>Provisional Invoice</dc:title>
<dc:title>Invoice</dc:title>
<meta:initial-creator>willen</meta:initial-creator>
<meta:generator>LibreOffice/7.6.0.3$Windows_X86_64 LibreOffice_project/69edd8b8ebc41d00b4de3915dc82f8f0fc3b6265</meta:generator>
<meta:editing-duration>PT3H16M29S</meta:editing-duration>
<meta:editing-cycles>9</meta:editing-cycles>
<meta:editing-duration>PT3H20M59S</meta:editing-duration>
<meta:editing-cycles>12</meta:editing-cycles>
<dc:date>2026-03-27T07:35:45.157000000</dc:date><meta:document-statistic meta:table-count="10" meta:image-count="2" meta:object-count="0" meta:page-count="2" meta:paragraph-count="61" meta:word-count="210" meta:character-count="2997" meta:non-whitespace-character-count="2429"/></office:meta>
<dc:date>2026-04-10T10:30:16.567000000</dc:date><meta:document-statistic meta:table-count="10" meta:image-count="2" meta:object-count="0" meta:page-count="2" meta:paragraph-count="66" meta:word-count="168" meta:character-count="2416" meta:non-whitespace-character-count="1896"/></office:meta>
<office:settings>
<config:config-item-set config:name="ooo:view-settings">
<config:config-item config:name="ViewAreaTop" config:type="long">28575</config:config-item>
<config:config-item config:name="ViewAreaTop" config:type="long">6879</config:config-item>
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaWidth" config:type="long">27264</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">13187</config:config-item>
@@ -23,12 +23,12 @@
<config:config-item-map-indexed config:name="Views">
<config:config-item-map-entry>
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">7583</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">33203</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">13275</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">15563</config:config-item>
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">28575</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">6879</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">27263</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">41760</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">20064</config:config-item>
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
@@ -101,7 +101,7 @@
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
<config:config-item config:name="Rsid" config:type="int">297701</config:config-item>
<config:config-item config:name="Rsid" config:type="int">523469</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">84510</config:config-item>
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
@@ -168,19 +168,20 @@
<style:font-face style:name="Lucida Sans1" svg:font-family="&apos;Lucida Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Microsoft YaHei" svg:font-family="&apos;Microsoft YaHei&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Tahoma" svg:font-family="Tahoma, arial" style:font-family-generic="swiss" style:font-pitch="variable"/>
<style:font-face style:name="Times New Roman" svg:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Times New Roman" svg:font-family="&apos;Times New Roman&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Times New Roman1" svg:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="true"/>
<style:paragraph-properties style:text-autospace="none" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
<style:paragraph-properties style:text-autospace="none" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman1" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman1" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman1" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="none" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.249cm" style:writing-mode="lr-tb"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman1" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman1" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman1" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
@@ -245,7 +246,7 @@
</style:style>
<style:style style:name="No_20_Spacing" style:display-name="No Spacing" style:family="paragraph">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-align="start" style:justify-single-word="false" fo:text-indent="0cm" style:auto-text-indent="false" style:text-autospace="ideograph-alpha" style:vertical-align="auto"/>
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="11pt" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="11pt" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="11pt" style:language-complex="ar" style:country-complex="SA"/>
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="11pt" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="11pt" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="11pt" style:language-complex="ar" style:country-complex="SA"/>
</style:style>
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
@@ -295,30 +296,35 @@
</style:paragraph-properties>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="Normal" style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit"/>
<style:text-properties fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:style>
<style:style style:name="Default_20_Paragraph_20_Font" style:display-name="Default Paragraph Font" style:family="text"/>
<style:style style:name="Titolo_20_1_20_Carattere" style:display-name="Titolo 1 Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Arial1" fo:font-family="Arial, Arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="12pt" fo:language="en" fo:country="US" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" fo:font-weight="bold" style:font-name-asian="Arial1" style:font-family-asian="Arial, Arial" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="12pt" style:language-asian="en" style:country-asian="US" style:font-weight-asian="bold" style:font-name-complex="Arial1" style:font-family-complex="Arial, Arial" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="12pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Intestazione_20_Carattere" style:display-name="Intestazione Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Times New Roman" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable" style:font-size-complex="10pt"/>
<style:text-properties style:font-name="Times New Roman1" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="Placeholder_20_Text" style:display-name="Placeholder Text" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties fo:color="#808080" loext:opacity="100%" style:font-name="Times New Roman" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
<style:text-properties fo:color="#808080" loext:opacity="100%" style:font-name="Times New Roman1" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman1" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="Testo_20_fumetto_20_Carattere" style:display-name="Testo fumetto Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Tahoma" fo:font-family="Tahoma, arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="8pt" style:font-name-asian="Tahoma" style:font-family-asian="Tahoma, arial" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="8pt" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma, arial" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="8pt"/>
</style:style>
<style:style style:name="PiÃ_a8__20_di_20_pagina_20_Carattere" style:display-name="Piè di pagina Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Times New Roman" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
<style:text-properties style:font-name="Times New Roman1" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman1" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="Nessuna_20_spaziatura_20_Carattere" style:display-name="Nessuna spaziatura Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable"/>
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="Placeholder" style:family="text">
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="Police_20_par_20_dÃ_a9_faut" style:display-name="Police par défaut" style:family="text"/>
<style:style style:name="Default_20_Paragraph_20_Font_20__28_WW_29_" style:display-name="Default Paragraph Font (WW)" style:family="text"/>
<style:style style:name="Police_20_par_20_défaut" style:display-name="Police par défaut" style:family="text"/>
<style:style style:name="Frame" style:family="graphic">
<style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph-content" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" fo:background-color="transparent" draw:fill="none" draw:fill-color="#cfe7f5"/>
</style:style>
@@ -664,7 +670,7 @@
</style:style>
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
@@ -684,7 +690,7 @@
</style:style>
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="US" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="US" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
@@ -696,7 +702,7 @@
</style:style>
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="center" style:justify-single-word="false" style:writing-mode="lr-tb"/>
@@ -708,7 +714,7 @@
</style:style>
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="end" style:justify-single-word="false" style:writing-mode="lr-tb"/>
@@ -724,7 +730,7 @@
</style:style>
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="1pt" fo:language="en" fo:country="US" style:font-size-asian="1pt" style:font-name-complex="Times New Roman" style:font-size-complex="1pt" text:display="none"/>
<style:text-properties fo:font-size="1pt" fo:language="en" fo:country="US" style:font-size-asian="1pt" style:font-name-complex="Times New Roman1" style:font-size-complex="1pt" text:display="none"/>
</style:style>
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
@@ -759,14 +765,34 @@
</style:paragraph-properties>
<style:text-properties officeooo:paragraph-rsid="00022035"/>
</style:style>
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" officeooo:paragraph-rsid="000391f3" style:font-size-asian="10pt" style:font-name-complex="Arial1" style:font-size-complex="10pt"/>
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="10pt" style:font-name-complex="Arial" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="footer">
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Calibri" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="10pt" style:font-name-complex="Arial" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties officeooo:paragraph-rsid="00056da2"/>
</style:style>
<style:style style:name="P47" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties style:font-name="Calibri" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P48" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties style:font-name="Calibri" fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Arial1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P49" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P50" style:family="paragraph" style:parent-style-name="footer">
<style:paragraph-properties style:writing-mode="lr-tb"/>
</style:style>
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="header">
<style:style style:name="P51" style:family="paragraph" style:parent-style-name="header">
<style:paragraph-properties style:writing-mode="lr-tb"/>
</style:style>
<style:style style:name="T1" style:family="text">
@@ -790,6 +816,24 @@
<style:style style:name="T7" style:family="text">
<style:text-properties fo:language="fr" fo:country="CH"/>
</style:style>
<style:style style:name="T8" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="9pt" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T9" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="9pt" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T10" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" fo:font-size="9pt" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T11" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" fo:font-size="9pt" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T12" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-name-complex="Arial"/>
</style:style>
<style:style style:name="T13" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-name-complex="Arial"/>
</style:style>
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
<style:graphic-properties style:run-through="background" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="top" style:vertical-rel="baseline" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
</style:style>
@@ -3866,7 +3910,7 @@
<table:table-row table:style-name="Tableau3.1">
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
<text:p text:style-name="P22"/>
<text:p text:style-name="P22">Provisional Invoice</text:p>
<text:p text:style-name="P22">Invoice</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
<text:p text:style-name="P22"/>
@@ -3930,15 +3974,19 @@
<text:p text:style-name="P25">Goods description</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau5.A1" office:value-type="string">
<text:p text:style-name="P26">QUANTITY: <text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_lbs, invoice.party.lang) if invoice.report_lbs != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/>LBS (<text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_net, invoice.party.lang) if invoice.report_net != &apos;&apos; else &apos;&apos;&gt;</text:placeholder> MTS)</text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;invoice.report_description_upper or invoice.report_product_description&gt;</text:placeholder><text:s/>CROP <text:placeholder text:placeholder-type="text">&lt;invoice.report_crop_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P26">QUANTITY: <text:placeholder text:placeholder-type="text">&lt;invoice.report_lbs_display&gt;</text:placeholder><text:s/>LBS (<text:placeholder text:placeholder-type="text">&lt;invoice.report_net_display&gt;</text:placeholder> <text:placeholder text:placeholder-type="text">&lt;invoice.report_weight_unit_upper&gt;</text:placeholder>)</text:p>
<text:p text:style-name="P26"/>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;invoice.report_description_upper or invoice.report_product_description&gt;</text:placeholder><text:placeholder text:placeholder-type="text">&lt;&apos; CROP &apos; + invoice.report_crop_name if invoice.report_crop_name else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;invoice.report_attributes_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P18">At <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_currency_upper&gt;</text:placeholder><text:s/><text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_value&gt;</text:placeholder><text:s/>PER <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_unit_upper&gt;</text:placeholder><text:s/>(<text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_price_words&gt;</text:placeholder>) <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_pricing_text&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;block in invoice.report_trade_blocks&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;block[0]&gt;</text:placeholder></text:p>
<text:p text:style-name="P18">At <text:placeholder text:placeholder-type="text">&lt;block[1]&gt;</text:placeholder></text:p>
<text:p text:style-name="P18"/>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P18"/>
<text:p text:style-name="P18"/>
<text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;invoice.report_incoterm&gt;</text:placeholder></text:p>
<text:p text:style-name="P29"><text:span text:style-name="T1">ALL DETAILS AND SPECIFICATIONS AS PER</text:span> <text:span text:style-name="T3">BENEFICIARY </text:span></text:p>
<text:p text:style-name="P26">PROFORMA INVOICE NO. <text:placeholder text:placeholder-type="text">&lt;invoice.report_proforma_invoice_number&gt;</text:placeholder><text:s/>DATED <text:placeholder text:placeholder-type="text">&lt;format_date(invoice.report_proforma_invoice_date, invoice.party.lang) if invoice.report_proforma_invoice_date else &apos;&apos;&gt;</text:placeholder>.</text:p>
<text:p text:style-name="P29"><text:span text:style-name="T7"><text:placeholder text:placeholder-type="text">&lt;invoice.report_incoterm&gt;</text:placeholder></text:span><text:span text:style-name="T3"><text:s/></text:span></text:p>
<text:p text:style-name="P26"/>
<text:p text:style-name="P26"/>
<text:p text:style-name="P12"/>
</table:table-cell>
@@ -3952,10 +4000,10 @@
<text:p text:style-name="P15"><text:s text:c="19"/>BALES</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
<text:p text:style-name="P41"><text:s text:c="13"/>Gross KGS</text:p>
<text:p text:style-name="P41"><text:s text:c="13"/>Gross <text:placeholder text:placeholder-type="text">&lt;invoice.report_weight_unit_upper&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
<text:p text:style-name="P15"><text:s text:c="13"/>NET KGS</text:p>
<text:p text:style-name="P15"><text:s text:c="13"/>NET <text:placeholder text:placeholder-type="text">&lt;invoice.report_weight_unit_upper&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
<text:p text:style-name="P41"><text:s text:c="10"/></text:p>
@@ -3969,10 +4017,10 @@
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;invoice.report_nb_bale&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;invoice.report_gross_display&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_net, invoice.party.lang) if invoice.report_net != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text">&lt;invoice.report_net_display&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P16"/>
@@ -3996,7 +4044,7 @@
<text:p text:style-name="P16">Equivalent to LBS</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau7.A1" office:value-type="string">
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_lbs, invoice.party.lang) if invoice.report_lbs != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text">&lt;invoice.report_lbs_display&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau7.A1" office:value-type="string">
<text:p text:style-name="P20"/>
@@ -4009,13 +4057,18 @@
<table:table-column table:style-name="Tableau8.B"/>
<table:table-row table:style-name="Tableau8.1">
<table:table-cell table:style-name="Tableau8.A1" office:value-type="string">
<text:p text:style-name="P14">At <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_currency_upper&gt;</text:placeholder><text:s/><text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_value&gt;</text:placeholder><text:s/>PER <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_unit_upper&gt;</text:placeholder><text:s/>(<text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_price_words&gt;</text:placeholder>) <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_pricing_text&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in invoice.report_rate_lines.splitlines()&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14">At <text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"/>
<text:p text:style-name="P14">FREIGHT VALUE: <text:placeholder text:placeholder-type="text">&lt;invoice.report_freight_currency_symbol&gt;</text:placeholder><text:s/><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_freight_amount, invoice.party.lang) if invoice.report_freight_amount != &apos;&apos; else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"/>
<text:p text:style-name="P17">WE CERTIFY THAT THE MERCHANDISE IS OF <text:span text:style-name="T7"><text:placeholder text:placeholder-type="text">&lt;invoice.report_origin or &apos;&apos;&gt;</text:placeholder></text:span><text:s/>ORIGIN</text:p>
<text:p text:style-name="P31"><text:soft-page-break/>L/C NUMBER </text:p>
<text:p text:style-name="P31"/>
<text:p text:style-name="P17"/>
<text:p text:style-name="P47"><text:span text:style-name="T12">B</text:span><text:span text:style-name="T13">ANK</text:span><text:span text:style-name="T12">: EFG BANK SA</text:span></text:p>
<text:p text:style-name="P45">IBAN : CH8808667007168111027</text:p>
<text:p text:style-name="P45">SwifT Code: EFGBCHZZ</text:p>
<text:p text:style-name="P48"/>
</table:table-cell>
<table:table-cell table:style-name="Tableau8.A1" office:value-type="string">
<text:p text:style-name="P34"><text:s text:c="3"/><text:placeholder text:placeholder-type="text">&lt;format_currency(invoice.total_amount, invoice.party.lang, invoice.currency)&gt;</text:placeholder><text:s/></text:p>
@@ -4027,6 +4080,7 @@
<text:p text:style-name="P11"/>
<table:table table:name="Tableau9" table:style-name="Tableau9">
<table:table-column table:style-name="Tableau9.A"/>
<text:soft-page-break/>
<table:table-row table:style-name="Tableau9.1">
<table:table-cell table:style-name="Tableau9.A1" office:value-type="string">
<text:p text:style-name="P21">NET LANDED WEIGHTS, ACTUAL TARE, NO FRANCHISE</text:p>
@@ -4052,7 +4106,7 @@
<text:p text:style-name="P13">Controller Name</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_reference&gt;</text:placeholder></text:p>
<text:p text:style-name="P25"/>
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p>
</table:table-cell>
@@ -4100,4 +4154,4 @@
<text:p text:style-name="P38"/>
</office:text>
</office:body>
</office:document>
</office:document>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
import datetime
from decimal import Decimal
from unittest.mock import Mock, patch
from trytond.modules.account_invoice.exceptions import (
PaymentTermValidationError)
@@ -251,5 +252,70 @@ class AccountInvoiceTestCase(
(datetime.date(2012, 1, 14), Decimal('-1.0')),
])
def test_post_rpc_does_not_require_fresh_session(self):
'posting invoices does not force a fresh session'
Invoice = Pool().get('account.invoice')
self.assertFalse(Invoice.__rpc__['post'].fresh_session)
@with_transaction()
def test_validate_invoice_creates_move_for_customer_invoice(self):
'validating customer invoices now creates the account move'
Invoice = Pool().get('account.invoice')
move = Mock()
invoice = Invoice()
invoice.type = 'out'
invoice.move = None
invoice.get_move = Mock(return_value=move)
invoice.do_lot_invoicing = Mock()
move_model = Mock()
with patch.object(Invoice, '_check_taxes'), patch.object(
Invoice, '_store_cache'), patch.object(
Invoice, 'browse', return_value=[]), patch.object(
Invoice, 'cleanMoves') as clean_moves, patch.object(
Invoice, 'save') as save_invoices, patch(
'trytond.modules.account_invoice.invoice.Pool'
) as PoolMock:
PoolMock.return_value.get.return_value = move_model
Invoice.validate_invoice([invoice])
self.assertIs(invoice.move, move)
invoice.get_move.assert_called_once_with()
invoice.do_lot_invoicing.assert_called_once_with()
move_model.save.assert_called_once_with([move])
clean_moves.assert_called_once_with([move])
save_invoices.assert_called()
@with_transaction()
def test_validate_invoice_sets_number_for_customer_invoice(self):
'validating customer invoices now assigns the invoice number'
Invoice = Pool().get('account.invoice')
move = Mock()
invoice = Invoice()
invoice.type = 'out'
invoice.move = None
invoice.get_move = Mock(return_value=move)
invoice.do_lot_invoicing = Mock()
move_model = Mock()
with patch.object(Invoice, '_check_taxes'), patch.object(
Invoice, '_store_cache'), patch.object(
Invoice, 'set_number') as set_number, patch.object(
Invoice, 'cleanMoves'), patch.object(
Invoice, 'save'), patch(
'trytond.modules.account_invoice.invoice.Pool'
) as PoolMock:
PoolMock.return_value.get.return_value = move_model
Invoice.validate_invoice([invoice])
set_number.assert_called_once_with([invoice])
del ModuleTestCase

View File

@@ -181,7 +181,11 @@ class InvoiceLine(metaclass=PoolMeta):
company=self.invoice.company.id, date=accounting_date):
anglo_saxon_move_lines = self._get_anglo_saxon_move_lines(
cost, type_)
if type_ == 'in_supplier' and (self.lot.sale_invoice_line_prov or self.lot.sale_invoice_line) and not self.fee:
lot_has_sale_invoice = (
self.lot
and (self.lot.sale_invoice_line_prov
or self.lot.sale_invoice_line))
if type_ == 'in_supplier' and lot_has_sale_invoice and not self.fee:
anglo_saxon_move_lines_ = self._get_anglo_saxon_move_lines(cost, 'out_customer')
result.extend(anglo_saxon_move_lines)
result.extend(anglo_saxon_move_lines_)

View File

@@ -2,8 +2,8 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook" position="inside">
<page string="Analytic" id="analytic_accounts">
<xpath expr="/form/notebook/page[@id='invoice']" position="before">
<page string="Analytics" id="analytic_accounts">
<field name="analytic_accounts" colspan="4"/>
</page>
</xpath>

View File

@@ -1,11 +1,16 @@
from trytond.pool import Pool
from . import automation,rules,freight_booking,cron #, document
def register():
Pool.register(
automation.AutomationDocument,
rules.AutomationRuleSet,
freight_booking.FreightBookingInfo,
cron.Cron,
cron.AutomationCron,
module='automation', type_='model')
from trytond.pool import Pool
from . import automation,rules,freight_booking,booking_report,cron #, document
def register():
Pool.register(
automation.AutomationDocument,
rules.AutomationRuleSet,
freight_booking.FreightBookingInfo,
booking_report.FreightBookingShipmentReport,
booking_report.FreightBookingShipmentReportLinkStart,
cron.Cron,
cron.AutomationCron,
module='automation', type_='model')
Pool.register(
booking_report.FreightBookingShipmentReportLink,
module='automation', type_='wizard')

View File

@@ -10,6 +10,7 @@ import io
import logging
import json
import re
import traceback
logger = logging.getLogger(__name__)
@@ -62,25 +63,57 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
@ModelView.button
def run_ocr(cls, docs):
for doc in docs:
logger.info(
"RUN_OCR_START doc=%s type=%s state=%s incoming=%s",
getattr(doc, 'id', None), getattr(doc, 'type', None),
getattr(doc, 'state', None),
getattr(getattr(doc, 'document', None), 'id', None))
try:
if doc.type == 'weight_report':
# Décoder le fichier depuis le champ Binary
file_data = doc.document.data or b""
logger.info(f"File size: {len(file_data)} bytes")
logger.info(f"First 20 bytes: {file_data[:20]}")
logger.info(f"Last 20 bytes: {file_data[-20:]}")
if not doc.document:
raise ValueError("No incoming document linked")
file_data = doc.document.data or b""
file_name = doc.document.name or "document"
url = "http://automation-service:8006/ocr"
logger.info(
"RUN_OCR_FILE doc=%s incoming=%s name=%s size=%s "
"first_20=%r last_20=%r",
getattr(doc, 'id', None), doc.document.id,
file_name, len(file_data), file_data[:20],
file_data[-20:])
if not file_data:
raise ValueError("Incoming document has no binary data")
# Envoyer le fichier au service OCR
logger.info(
"RUN_OCR_HTTP_REQUEST doc=%s url=%s file_name=%s "
"size=%s",
getattr(doc, 'id', None), url, file_name,
len(file_data))
response = requests.post(
"http://automation-service:8006/ocr",
files={"file": (file_name, io.BytesIO(file_data))}
url,
files={"file": (file_name, io.BytesIO(file_data))},
timeout=120,
)
logger.info(
"RUN_OCR_HTTP_RESPONSE doc=%s status=%s reason=%s "
"elapsed=%s content_type=%s body_start=%r",
getattr(doc, 'id', None), response.status_code,
response.reason, response.elapsed,
response.headers.get('content-type'),
response.text[:1000])
response.raise_for_status()
data = response.json()
logger.info("RUN_OCR_RESPONSE:%s",data)
doc.ocr_text = data.get("ocr_text", "")
ocr_text = data.get("ocr_text", "") or ""
logger.info(
"RUN_OCR_JSON doc=%s keys=%s ocr_text_len=%s "
"ocr_text_start=%r",
getattr(doc, 'id', None), list(data.keys()),
len(ocr_text), ocr_text[:500])
doc.ocr_text = ocr_text
doc.state = "ocr_done"
doc.notes = (doc.notes or "") + "OCR done\n"
else:
@@ -112,9 +145,17 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
doc.notes = (doc.notes or "") + " SO updated"
except Exception as e:
logger.error(
"RUN_OCR_FAILED doc=%s type=%s error=%s traceback=%s",
getattr(doc, 'id', None), getattr(doc, 'type', None),
e, traceback.format_exc())
doc.state = "error"
doc.notes = (doc.notes or "") + f"OCR error: {e}\n"
doc.save()
logger.info(
"RUN_OCR_END doc=%s state=%s ocr_text_len=%s",
getattr(doc, 'id', None), getattr(doc, 'state', None),
len(getattr(doc, 'ocr_text', None) or ""))
# -------------------------------------------------------
# STRUCTURE (doctr)
# -------------------------------------------------------
@@ -248,29 +289,10 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
ShipmentWR.save([swr])
doc.notes = (doc.notes or "") + f"Shipment found: {sh[0].number}\n"
logger.info("BL_NUMBER:%s",sh[0].bl_number)
if sh[0].incoming_moves:
factor_net = wr.net_landed_kg / wr.bales if wr.bales else 1
factor_gross = wr.gross_landed_kg / wr.bales if wr.bales else 1
for move in sh[0].incoming_moves:
lot = move.lot
if lot.lot_type == 'physic':
wr_payload = {
"chunk_key": lot.lot_chunk_key,
"gross_weight": float(round(Decimal(lot.lot_qt) * factor_gross,5)),
"net_weight": float(round(Decimal(lot.lot_qt) * factor_net,5)),
"tare_total": float(round(wr.tare_kg * (Decimal(lot.lot_qt) / wr.bales),5)) ,
"bags": int(lot.lot_qt),
"surveyor_code": sh[0].controller.get_alf(),
"place_key": sh[0].to_location.get_places(),
"report_date": int(wr.report_date.strftime("%Y%m%d")),#wr.report_date.isoformat() if wr.report_date else None,
"weight_date": int(wr.weight_date.strftime("%Y%m%d")),#wr.weight_date.isoformat() if wr.weight_date else None,
"agent": sh[0].agent.get_alf(),
"forwarder_ref": sh[0].returned_id
}
logger.info("PAYLOAD:%s",wr_payload)
data = doc.create_weight_report(wr_payload)
doc.notes = (doc.notes or "") + f"WR created in Fintrade: {data.get('success')}\n"
doc.notes = (doc.notes or "") + f"WR key: {data.get('weight_report_key')}\n"
doc.notes = (
(doc.notes or "")
+ "Global WR linked to shipment. "
+ "Create remote lot WRs from the weight report form.\n")
# if cls.rule_set.ocr_required:[]
# cls.run_ocr([doc])
@@ -293,4 +315,4 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
# except Exception as e:
# doc.state = "error"
# doc.notes = (doc.notes or "") + f"Pipeline error: {e}\n"
doc.save()
doc.save()

View File

@@ -0,0 +1,193 @@
import json
from sql import Literal, Null
from sql.functions import CurrentTimestamp
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.wizard import Button, StateTransition, StateView, Wizard
from trytond.exceptions import UserError
class FreightBookingShipmentReport(ModelSQL, ModelView):
"Freight Booking Shipment Report"
__name__ = 'freight.booking.shipment.report'
automation_document = fields.Many2One(
'automation.document', "Automation Document")
bl_number = fields.Function(fields.Char("BL Number"), 'get_bl_number')
shipment = fields.Function(fields.Many2One(
'stock.shipment.in', "Shipment"), 'get_shipment')
status = fields.Function(fields.Selection([
('found', "Found"),
('error', "Error"),
], "Status"), 'get_status')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order = [
('id', 'DESC'),
]
@classmethod
def table_query(cls):
AutomationDocument = Pool().get('automation.document')
document = AutomationDocument.__table__()
return document.select(
Literal(None).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Literal(None).as_('write_uid'),
Literal(None).as_('write_date'),
document.id.as_('id'),
document.id.as_('automation_document'),
where=(
(document.type == 'weight_report')
& (document.metadata_json != Null)))
@classmethod
def _extract_bl_number(cls, metadata_json):
if not metadata_json:
return None
try:
metadata = json.loads(str(metadata_json))
except (TypeError, ValueError):
return None
def find_value(value):
if isinstance(value, dict):
for key in (
'bl_no', 'bl_number', 'bl', 'BL_Number',
'BL number', 'B/L No'):
bl_number = value.get(key)
if bl_number:
return str(bl_number).strip()
for item in value.values():
bl_number = find_value(item)
if bl_number:
return bl_number
elif isinstance(value, list):
for item in value:
bl_number = find_value(item)
if bl_number:
return bl_number
return None
return find_value(metadata)
@classmethod
def _get_report_data(cls, reports):
ShipmentIn = Pool().get('stock.shipment.in')
bl_numbers = {}
for report in reports:
document = report.automation_document
bl_number = cls._extract_bl_number(
document.metadata_json if document else None)
bl_numbers[report.id] = bl_number
shipments_by_bl = {}
for bl_number in {b for b in bl_numbers.values() if b}:
shipments = ShipmentIn.search([
('bl_number', '=', bl_number),
], limit=1)
if not shipments:
shipments = ShipmentIn.search([
('bl_number', 'ilike', bl_number),
], limit=1)
shipments_by_bl[bl_number] = shipments[0] if shipments else None
return bl_numbers, shipments_by_bl
@classmethod
def get_bl_number(cls, reports, name):
bl_numbers, _ = cls._get_report_data(reports)
return bl_numbers
@classmethod
def get_shipment(cls, reports, name):
bl_numbers, shipments_by_bl = cls._get_report_data(reports)
return {
report.id: (
shipments_by_bl[bl_numbers[report.id]].id
if bl_numbers[report.id]
and shipments_by_bl.get(bl_numbers[report.id])
else None)
for report in reports
}
@classmethod
def get_status(cls, reports, name):
bl_numbers, shipments_by_bl = cls._get_report_data(reports)
return {
report.id: (
'found'
if bl_numbers[report.id]
and shipments_by_bl.get(bl_numbers[report.id])
else 'error')
for report in reports
}
class FreightBookingShipmentReportLinkStart(ModelView):
"Link Freight Booking Shipment Report"
__name__ = 'freight.booking.shipment.report.link.start'
bl_number = fields.Char("BL Number", readonly=True)
shipment = fields.Many2One(
'stock.shipment.in', "Shipment", required=True)
class FreightBookingShipmentReportLink(Wizard):
"Link Freight Booking Shipment Report"
__name__ = 'freight.booking.shipment.report.link'
start = StateView(
'freight.booking.shipment.report.link.start',
'automation.freight_booking_shipment_report_link_start_form',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button('Link', 'link', 'tryton-ok', default=True),
])
link = StateTransition()
def default_start(self, fields):
Report = Pool().get('freight.booking.shipment.report')
active_ids = Transaction().context.get('active_ids') or []
reports = Report.browse(active_ids)
error_reports = [
report for report in reports
if report.status == 'error' and report.bl_number]
if len(error_reports) > 1:
raise UserError(
"Link one error line at a time to avoid overwriting the "
"shipment BL number.")
return {
'bl_number': error_reports[0].bl_number if error_reports else None,
}
def transition_link(self):
Report = Pool().get('freight.booking.shipment.report')
ShipmentIn = Pool().get('stock.shipment.in')
active_ids = Transaction().context.get('active_ids') or []
reports = Report.browse(active_ids)
error_reports = [
report for report in reports
if report.status == 'error' and report.bl_number]
if not error_reports:
raise UserError(
"Select at least one error line with a BL number.")
if len(error_reports) > 1:
raise UserError(
"Link one error line at a time to avoid overwriting the "
"shipment BL number.")
shipment = self.start.shipment
shipment.bl_number = error_reports[0].bl_number
ShipmentIn.save([shipment])
return 'end'
def end(self):
return 'reload'

View File

@@ -0,0 +1,73 @@
<?xml version="1.0"?>
<tryton>
<data>
<record model="ir.ui.view" id="freight_booking_shipment_report_tree">
<field name="model">freight.booking.shipment.report</field>
<field name="type">tree</field>
<field name="name">freight_booking_shipment_report_tree</field>
</record>
<record model="ir.action.act_window" id="act_freight_booking_shipment_report">
<field name="name">Freight Booking Shipment Report</field>
<field name="res_model">freight.booking.shipment.report</field>
</record>
<record model="ir.action.act_window.view" id="act_freight_booking_shipment_report_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="freight_booking_shipment_report_tree"/>
<field name="act_window" ref="act_freight_booking_shipment_report"/>
</record>
<record model="ir.ui.view" id="freight_booking_shipment_report_link_start_form">
<field name="model">freight.booking.shipment.report.link.start</field>
<field name="type">form</field>
<field name="name">freight_booking_shipment_report_link_start_form</field>
</record>
<record model="ir.action.wizard" id="act_freight_booking_shipment_report_link">
<field name="name">Link to Shipment</field>
<field name="wiz_name">freight.booking.shipment.report.link</field>
</record>
<record model="ir.action.keyword" id="keyword_freight_booking_shipment_report_link">
<field name="keyword">form_action</field>
<field name="model">freight.booking.shipment.report,-1</field>
<field name="action" ref="act_freight_booking_shipment_report_link"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report">
<field name="model">freight.booking.shipment.report</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report_link_start">
<field name="model">freight.booking.shipment.report.link.start</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report_link_start_automation">
<field name="model">freight.booking.shipment.report.link.start</field>
<field name="group" ref="group_automation"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report_automation">
<field name="model">freight.booking.shipment.report</field>
<field name="group" ref="group_automation"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<menuitem
name="Freight Booking Shipment Report"
action="act_freight_booking_shipment_report"
parent="menu_automation"
sequence="11"
id="menu_freight_booking_shipment_report" />
</data>
</tryton>

View File

@@ -11,6 +11,7 @@ import traceback
logger = logging.getLogger(__name__)
class Cron(metaclass=PoolMeta):
__name__ = 'ir.cron'
@@ -18,9 +19,11 @@ class Cron(metaclass=PoolMeta):
def __setup__(cls):
super().__setup__()
cls.method.selection.append(
('automation.cron|update_shipment', "Update Shipment from freight booking info")
('automation.cron|update_shipment',
"Update Shipment from freight booking info")
)
class AutomationCron(ModelSQL, ModelView):
"Automation Cron"
__name__ = 'automation.cron'
@@ -30,7 +33,7 @@ class AutomationCron(ModelSQL, ModelView):
('weekly', "Weekly"),
('monthly', "Monthly"),
], "Frequency", required=True,
help="How frequently rates must be updated.")
help="How frequently rates must be updated.")
last_update = fields.Date("Last Update", required=True)
@@ -70,22 +73,23 @@ class AutomationCron(ModelSQL, ModelView):
))
rows = cursor.fetchall()
logger.info(f"Nombre total de lignes à traiter : {len(rows)}")
logger.info(f"Nombre total de lignes a traiter : {len(rows)}")
# ---- PREMIÈRE TRANSACTION : Création des objets de référence ----
# Premiere transaction : creation des objets de reference
with Transaction().new_transaction() as trans1:
try:
logger.info("Début de la création des objets de référence...")
logger.info(
"Debut de la creation des objets de reference...")
parties_to_save = []
vessels_to_save = []
locations_to_save = []
parties_cache = {}
vessels_cache = {}
locations_cache = {}
# Collecter les données des objets de référence
# Collecter les donnees des objets de reference
for row in rows:
(
si_number, si_date, si_quantity, si_unit,
@@ -97,47 +101,44 @@ class AutomationCron(ModelSQL, ModelView):
comments, fintrade_booking_key
) = row
# Fonction pour obtenir ou créer un Party
def get_or_create_party(name):
if not name:
return None
name_upper = str(name).strip().upper()
if name_upper in parties_cache:
return parties_cache[name_upper]
# Chercher d'abord dans la base
existing = Party.search([('name', '=', name_upper)], limit=1)
existing = Party.search(
[('name', '=', name_upper)], limit=1)
if existing:
parties_cache[name_upper] = existing[0]
return existing[0]
# Créer un nouveau
new_p = Party()
new_p.name = name_upper
parties_cache[name_upper] = new_p
parties_to_save.append(new_p)
return new_p
# Fonction pour obtenir ou créer un Vessel
def get_or_create_vessel(name):
if not name:
return None
name_upper = str(name).strip().upper()
if name_upper in vessels_cache:
return vessels_cache[name_upper]
existing = Vessel.search([('vessel_name', '=', name_upper)], limit=1)
existing = Vessel.search(
[('vessel_name', '=', name_upper)], limit=1)
if existing:
vessels_cache[name_upper] = existing[0]
return existing[0]
new_v = Vessel()
new_v.vessel_name = name_upper
vessels_cache[name_upper] = new_v
vessels_to_save.append(new_v)
return new_v
# Fonction pour obtenir ou créer une Location
def get_or_create_location(name, type_):
if not name:
return None
@@ -145,16 +146,15 @@ class AutomationCron(ModelSQL, ModelView):
key = f"{name_upper}_{type_}"
if key in locations_cache:
return locations_cache[key]
existing = Location.search([
('name', '=', name_upper),
('type', '=', type_)
('type', '=', type_),
], limit=1)
if existing:
locations_cache[key] = existing[0]
return existing[0]
new_loc = Location()
new_loc.name = name_upper
new_loc.type = type_
@@ -162,40 +162,40 @@ class AutomationCron(ModelSQL, ModelView):
locations_to_save.append(new_loc)
return new_loc
# Collecter les objets à créer
_ = get_or_create_party(carrier_name)
_ = get_or_create_party(agent_name)
_ = get_or_create_vessel(vessel_name)
_ = get_or_create_location(loading_name, 'supplier')
_ = get_or_create_location(destination_name, 'customer')
# Sauvegarder tous les objets de référence
if parties_to_save:
logger.info(f"Création de {len(parties_to_save)} parties...")
logger.info(f"Creation de {len(parties_to_save)} parties...")
Party.save(parties_to_save)
if vessels_to_save:
logger.info(f"Création de {len(vessels_to_save)} vessels...")
logger.info(f"Creation de {len(vessels_to_save)} vessels...")
Vessel.save(vessels_to_save)
if locations_to_save:
logger.info(f"Création de {len(locations_to_save)} locations...")
logger.info(
f"Creation de {len(locations_to_save)} locations...")
Location.save(locations_to_save)
trans1.commit()
logger.info("Première transaction commitée : objets de référence créés")
logger.info(
"Premiere transaction commitee : objets de reference crees")
except Exception as e:
trans1.rollback()
logger.error(f"Erreur dans la création des objets de référence : {e}")
logger.error(
f"Erreur dans la creation des objets de reference : {e}")
logger.error(traceback.format_exc())
raise
# ---- TRANSACTIONS INDIVIDUELLES pour chaque shipment ----
# Transactions individuelles pour chaque shipment
successful_shipments = 0
failed_shipments = []
# Recréer le curseur après la nouvelle transaction
cursor2 = Transaction().connection.cursor()
cursor2.execute(*t.select(
t.ShippingInstructionNumber,
@@ -218,7 +218,76 @@ class AutomationCron(ModelSQL, ModelView):
))
rows2 = cursor2.fetchall()
def normalize_bl_number(value):
value = str(value or '').strip()
if value.lower() in {'none', 'null'}:
return ''
if value.upper() in {
'MULTIPLE BL NUMBERS FOUND',
'MULTIPLE B/L NUMBERS FOUND',
'NO BL NUMBER FOUND',
'NO B/L NUMBER FOUND',
}:
return ''
return value
def normalize_booking_number(value):
value = str(value or '').strip()
if value.lower() in {'none', 'null'}:
return ''
return value
def sync_shipment_from_booking(shipment, bl_number, bl_date, etd_date,
controller):
changed = False
columns = []
values = []
booking_bl_number = normalize_bl_number(bl_number)
shipment_bl_number = normalize_bl_number(shipment.bl_number)
if booking_bl_number and booking_bl_number != shipment_bl_number:
logger.info(
"FREIGHT_BOOKING_SYNC_BL shipment=%s reference=%s "
"old_bl=%s new_bl=%s",
shipment.id, shipment.reference,
shipment.bl_number, booking_bl_number)
shipment.bl_number = booking_bl_number
columns.append('bl_number')
values.append(booking_bl_number)
changed = True
if bl_date and shipment.bl_date != bl_date:
logger.info(
"FREIGHT_BOOKING_SYNC_BL_DATE shipment=%s reference=%s "
"old_bl_date=%s new_bl_date=%s",
shipment.id, shipment.reference,
shipment.bl_date, bl_date)
shipment.bl_date = bl_date
columns.append('bl_date')
values.append(bl_date)
changed = True
if etd_date and shipment.etd != etd_date:
logger.info(
"FREIGHT_BOOKING_SYNC_ETD shipment=%s reference=%s "
"old_etd=%s new_etd=%s",
shipment.id, shipment.reference,
shipment.etd, etd_date)
shipment.etd = etd_date
columns.append('etd')
values.append(etd_date)
changed = True
if controller and shipment.controller_target != controller:
shipment.controller_target = controller
columns.append('controller_target')
values.append(controller)
changed = True
if changed:
table = ShipmentIn.__table__()
table_columns = [getattr(table, column) for column in columns]
cursor = Transaction().connection.cursor()
cursor.execute(*table.update(
table_columns, values, where=table.id == shipment.id))
return changed
for i, row in enumerate(rows2, 1):
(
si_number, si_date, si_quantity, si_unit,
@@ -229,46 +298,98 @@ class AutomationCron(ModelSQL, ModelView):
etd_date, bl_date, controller,
comments, fintrade_booking_key
) = row
logger.info(f"Traitement shipment {i}/{len(rows2)} : SI {si_number}")
# ---- TRANSACTION INDIVIDUELLE pour ce shipment ----
si_reference = normalize_booking_number(si_number)
logger.info(
"Traitement shipment %s/%s : SI %s reference=%s",
i, len(rows2), si_number, si_reference)
try:
with Transaction().new_transaction() as trans_shipment:
logger.info(f"Début transaction pour SI {si_number}")
# Vérifier si le shipment existe déjà
existing_shipment = ShipmentIn.search([
('reference', '=', si_number)
], limit=1)
if existing_shipment:
logger.info(f"Shipment {si_number} existe déjà, ignoré")
logger.info(
"Debut transaction pour SI %s reference=%s",
si_number, si_reference)
existing_shipments = ShipmentIn.search([
('reference', '=', si_reference)
])
if existing_shipments:
if len(existing_shipments) > 1:
logger.info(
"FREIGHT_BOOKING_DUPLICATE_SHIPMENT_REFERENCE "
"reference=%s shipment_ids=%s",
si_reference,
[shipment.id for shipment in existing_shipments])
for existing in existing_shipments:
sync_shipment_from_booking(
existing, bl_number, bl_date, etd_date,
controller)
shipment = existing_shipments[0]
if shipment.incoming_moves:
logger.info(
"Shipment %s existe deja avec lots, "
"booking data synchronized",
si_reference)
trans_shipment.commit()
continue
logger.info(
"Shipment %s existe deja sans lots, verification freight_booking_lots",
si_reference)
with Transaction().set_context(
_purchase_trade_skip_physical_lot_tolerance_warning=True):
inv_date, inv_nb = (
shipment._create_lots_from_fintrade())
shipment = ShipmentIn(shipment.id)
if shipment.incoming_moves:
shipment.controller = shipment.get_controller()
shipment.controller_target = controller
if not shipment.fees:
shipment.create_fee(shipment.controller)
shipment.instructions = shipment.get_instructions_html(
inv_date, inv_nb)
ShipmentIn.save([shipment])
logger.info(
"Shipment %s mis a jour avec %s incoming move(s)",
si_reference, len(shipment.incoming_moves))
else:
logger.info(
"Shipment %s existe sans lots et aucun lot disponible pour l'instant",
si_reference)
trans_shipment.commit()
continue
# Récupérer les objets (maintenant ils existent dans la base)
carrier = None
if carrier_name:
carrier_list = Party.search([('name', '=', str(carrier_name).strip().upper())], limit=1)
carrier_list = Party.search([
('name', '=',
str(carrier_name).strip().upper())
], limit=1)
if carrier_list:
carrier = carrier_list[0]
logger.info(f"Carrier trouvé pour {si_number}: {carrier.name}")
logger.info(
f"Carrier trouve pour {si_number}: {carrier.name}")
else:
logger.warning(f"Carrier NON TROUVÉ pour {si_number}: '{carrier_name}'")
logger.warning(
f"Carrier NON TROUVE pour {si_number}: '{carrier_name}'")
agent = None
agent_list = Party.search([('name', '=', str(agent_name or 'TBN').strip().upper())], limit=1)
agent_list = Party.search([
('name', '=', str(agent_name or 'TBN').strip().upper())
], limit=1)
if agent_list:
agent = agent_list[0]
vessel = None
if vessel_name:
vessel_list = Vessel.search([('vessel_name', '=', str(vessel_name).strip().upper())], limit=1)
vessel_list = Vessel.search([
('vessel_name', '=',
str(vessel_name).strip().upper())
], limit=1)
if vessel_list:
vessel = vessel_list[0]
loc_from = None
if loading_name:
loc_from_list = Location.search([
@@ -277,7 +398,7 @@ class AutomationCron(ModelSQL, ModelView):
], limit=1)
if loc_from_list:
loc_from = loc_from_list[0]
loc_to = None
if destination_name:
loc_to_list = Location.search([
@@ -286,19 +407,19 @@ class AutomationCron(ModelSQL, ModelView):
], limit=1)
if loc_to_list:
loc_to = loc_to_list[0]
# Vérification critique du carrier
if not carrier:
error_msg = f"ERREUR CRITIQUE: Carrier manquant pour SI {si_number} (valeur: '{carrier_name}')"
error_msg = (
f"ERREUR CRITIQUE: Carrier manquant pour SI {si_number} "
f"(valeur: '{carrier_name}')")
logger.error(error_msg)
raise ValueError(error_msg)
# Créer le shipment
shipment = ShipmentIn()
shipment.reference = si_number
shipment.reference = si_reference
shipment.from_location = loc_from
shipment.to_location = loc_to
shipment.carrier = None #carrier
shipment.carrier = None # carrier
shipment.supplier = agent
shipment.agent = agent
shipment.vessel = vessel
@@ -307,21 +428,30 @@ class AutomationCron(ModelSQL, ModelView):
shipment.bl_date = bl_date
shipment.etd = etd_date
shipment.etad = shipment.bl_date + timedelta(days=20)
# Sauvegarder ce shipment uniquement
ShipmentIn.save([shipment])
inv_date,inv_nb = shipment._create_lots_from_fintrade()
shipment.controller = shipment.get_controller()
shipment.controller_target = controller
shipment.create_fee(shipment.controller)
shipment.instructions = shipment.get_instructions_html(inv_date,inv_nb)
ShipmentIn.save([shipment])
with Transaction().set_context(
_purchase_trade_skip_physical_lot_tolerance_warning=True):
inv_date, inv_nb = shipment._create_lots_from_fintrade()
shipment = ShipmentIn(shipment.id)
if shipment.incoming_moves:
shipment.controller = shipment.get_controller()
shipment.controller_target = controller
shipment.create_fee(shipment.controller)
shipment.instructions = shipment.get_instructions_html(
inv_date, inv_nb)
ShipmentIn.save([shipment])
else:
logger.info(
"Shipment %s cree sans lots : fees/instructions "
"non crees, voir FINTRADE_CLEANUP_NEEDED",
si_number)
trans_shipment.commit()
successful_shipments += 1
logger.info(f"✓ Shipment {si_number} créé avec succès")
if shipment.incoming_moves:
successful_shipments += 1
logger.info(f"Shipment {si_number} cree avec succes")
except Exception as e:
# Cette transaction échoue mais les autres continuent
error_details = {
'si_number': si_number,
'carrier_name': carrier_name,
@@ -329,8 +459,8 @@ class AutomationCron(ModelSQL, ModelView):
'traceback': traceback.format_exc()
}
failed_shipments.append(error_details)
logger.error(f"ERREUR pour shipment {si_number}: {e}")
logger.error(f"ERREUR pour shipment {si_number}: {e}")
logger.error(f" Carrier: '{carrier_name}'")
logger.error(f" Agent: '{agent_name}'")
logger.error(f" Vessel: '{vessel_name}'")
@@ -338,24 +468,22 @@ class AutomationCron(ModelSQL, ModelView):
for line in traceback.format_exc().split('\n'):
if line.strip():
logger.error(f" {line}")
# ---- RÉSUMÉ FINAL ----
logger.info("=" * 60)
logger.info("RÉSUMÉ DE L'EXÉCUTION")
logger.info("RESUME DE L'EXECUTION")
logger.info("=" * 60)
logger.info(f"Total de shipments à traiter : {len(rows2)}")
logger.info(f"Shipments créés avec succès : {successful_shipments}")
logger.info(f"Shipments en échec : {len(failed_shipments)}")
logger.info(f"Total de shipments a traiter : {len(rows2)}")
logger.info(f"Shipments crees avec succes : {successful_shipments}")
logger.info(f"Shipments en echec : {len(failed_shipments)}")
if failed_shipments:
logger.info("\nDétail des échecs :")
logger.info("\nDetail des echecs :")
for i, error in enumerate(failed_shipments, 1):
logger.info(f" {i}. SI {error['si_number']}:")
logger.info(f" Carrier: '{error['carrier_name']}'")
logger.info(f" Erreur: {error['error']}")
# Log supplémentaire pour debug
logger.info("\nAnalyse des carriers problématiques :")
logger.info("\nAnalyse des carriers problematiques :")
problematic_carriers = {}
for error in failed_shipments:
carrier = error['carrier_name']
@@ -363,15 +491,16 @@ class AutomationCron(ModelSQL, ModelView):
problematic_carriers[carrier] += 1
else:
problematic_carriers[carrier] = 1
for carrier, count in problematic_carriers.items():
logger.info(f" Carrier '{carrier}' : {count} échec(s)")
# Vérifier si ce carrier existe dans la base
existing = Party.search([('name', '=', str(carrier).strip().upper())], limit=1)
logger.info(f" Carrier '{carrier}' : {count} echec(s)")
existing = Party.search([
('name', '=', str(carrier).strip().upper())
], limit=1)
if existing:
logger.info(f" → EXISTE DANS LA BASE (ID: {existing[0].id})")
logger.info(
f" -> EXISTE DANS LA BASE (ID: {existing[0].id})")
else:
logger.info(f" N'EXISTE PAS DANS LA BASE")
logger.info("=" * 60)
logger.info(" -> N'EXISTE PAS DANS LA BASE")
logger.info("=" * 60)

View File

@@ -7,4 +7,5 @@ depends:
xml:
automation.xml
freight_booking.xml
cron.xml
booking_report.xml
cron.xml

View File

@@ -0,0 +1,6 @@
<form>
<label name="bl_number"/>
<field name="bl_number"/>
<label name="shipment"/>
<field name="shipment"/>
</form>

View File

@@ -0,0 +1,7 @@
<tree>
<field name="bl_number"/>
<field name="status" widget="badge"
badge_colors="found:#16a34a,Found:#16a34a,error:#ef4444,Error:#ef4444"/>
<field name="shipment"/>
<field name="automation_document"/>
</tree>

View File

@@ -133,6 +133,19 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
if number.type == 'iban':
return number.number
@staticmethod
def _bic_compact(bic):
return getattr(bic, 'compact', bic)
@classmethod
def _bic_matches(cls, first, second):
def normalize(value):
value = cls._bic_compact(value)
if len(value) == 11 and value.endswith('XXX'):
value = value[:8]
return value
return normalize(first) == normalize(second)
@classmethod
def validate(cls, accounts):
super().validate(accounts)
@@ -146,7 +159,7 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
iban = IBAN(self.iban)
bic = BIC(self.bank.bic)
if (iban.bic
and iban.bic != bic
and not self._bic_matches(iban.bic, bic)
and (
iban.country_code != bic.country_code
or (iban.bank_code or iban.branch_code)

View File

@@ -41,6 +41,16 @@ class BankTestCase(ModuleTestCase):
bank.bic = 'foo'
bank.save()
@with_transaction()
def test_bic_matches_primary_branch(self):
"Test BIC matches main branch"
pool = Pool()
Account = pool.get('bank.account')
self.assertTrue(Account._bic_matches('CIALCHBBXXX', 'CIALCHBB'))
self.assertTrue(Account._bic_matches('CIALCHBB', 'CIALCHBBXXX'))
self.assertFalse(Account._bic_matches('CIALCHBB123', 'CIALCHBB'))
@with_transaction()
def test_iban_format(self):
'Test IBAN format'

View File

@@ -2,9 +2,4 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//page[@id='costs']" position="inside">
<field name="before_carriages" colspan="4"/>
<field name="after_carriages" colspan="4"/>
</xpath>
</data>

View File

@@ -295,23 +295,60 @@ class Incoming(DeactivableMixin, Workflow, ModelSQL, ModelView):
@ModelView.button
@Workflow.transition('processing')
def process(cls, documents, with_children=False):
logger.info(
"DOCUMENT_INCOMING_PROCESS_CLICK documents=%s with_children=%s",
[
{
'id': document.id,
'name': document.name,
'type': document.type,
'state': document.state,
}
for document in documents
],
with_children)
transaction = Transaction()
context = transaction.context
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
logger.info(
"DOCUMENT_INCOMING_PROCESS_QUEUE documents=%s queue_batch=%s",
[document.id for document in documents],
transaction.context.get('queue_batch'))
cls.__queue__.proceed(documents, with_children=with_children)
@classmethod
@ModelView.button
@Workflow.transition('done')
def proceed(cls, documents, with_children=False):
logger.info(
"DOCUMENT_INCOMING_PROCEED_START documents=%s with_children=%s",
[
{
'id': document.id,
'name': document.name,
'type': document.type,
'state': document.state,
'active': document.active,
'result': str(document.result) if document.result else None,
}
for document in documents
],
with_children)
pool = Pool()
Attachment = pool.get('ir.attachment')
results = defaultdict(list)
attachments = []
for document in documents:
if document.result or not document.active:
logger.info(
"DOCUMENT_INCOMING_PROCEED_SKIP document=%s "
"result=%s active=%s",
document.id, document.result, document.active)
continue
logger.info(
"DOCUMENT_INCOMING_PROCEED_DISPATCH document=%s method=%s",
document.id, f'_process_{document.type}')
document.result = getattr(document, f'_process_{document.type}')()
results[document.result.__class__].append(document.result)
attachment = Attachment(

View File

@@ -7,6 +7,9 @@ from trytond.modules.document_incoming.exceptions import (
DocumentIncomingProcessError)
from trytond.pool import Pool, PoolMeta
import json
import logging
logger = logging.getLogger(__name__)
class IncomingConfiguration(metaclass=PoolMeta):
@@ -18,6 +21,13 @@ class IncomingConfiguration(metaclass=PoolMeta):
class Incoming(metaclass=PoolMeta):
__name__ = 'document.incoming'
created_at = fields.Function(
fields.Date("Created At"),
'get_created_at')
result_notes = fields.Function(
fields.Text("Result Notes"),
'get_result_notes')
@classmethod
def __setup__(cls):
super().__setup__()
@@ -25,21 +35,32 @@ class Incoming(metaclass=PoolMeta):
('weight_report', "Weight Report"))
cls.type.selection.append(
('controller', "Controller"))
cls._order = [('create_date', 'DESC')]
@classmethod
def _get_results(cls):
return super()._get_results() | {'automation.document'}
def _process_weight_report(self):
logger.info(
"DOCUMENT_INCOMING_WR_START incoming=%s name=%s type=%s "
"data_size=%s",
getattr(self, 'id', None), getattr(self, 'name', None),
getattr(self, 'type', None),
len(getattr(self, 'data', None) or b""))
WR = Pool().get('automation.document')
wr = WR()
wr.document = self.id
wr.type = 'weight_report'
wr.state = 'draft'
WR.save([wr])
logger.info(
"DOCUMENT_INCOMING_WR_AUTOMATION_CREATED incoming=%s "
"automation_document=%s",
getattr(self, 'id', None), getattr(wr, 'id', None))
WR.run_ocr([wr])
WR.run_metadata([wr])
WR.run_pipeline([wr])
return wr
def _process_controller(self):
@@ -54,6 +75,25 @@ class Incoming(metaclass=PoolMeta):
return wr
def get_created_at(self, name=None):
create_date = getattr(self, 'create_date', None)
if not create_date:
return None
return create_date.date()
@classmethod
def order_created_at(cls, tables):
table, _ = tables[None]
return [table.create_date]
def get_result_notes(self, name=None):
result = getattr(self, 'result', None)
if not result:
return ''
if getattr(result, '__name__', None) == 'automation.document':
return getattr(result, 'notes', '') or ''
return ''
# @property
# def supplier_invoice_company(self):
# pass

View File

@@ -8,5 +8,10 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="document_incoming.document_incoming_configuration_view_form"/>
<field name="name">document_incoming_configuration_form</field>
</record>
<record model="ir.ui.view" id="document_incoming_view_list_wr">
<field name="model">document.incoming</field>
<field name="inherit" ref="document_incoming.document_incoming_view_list"/>
<field name="name">document_incoming_list</field>
</record>
</data>
</tryton>

View File

@@ -2,7 +2,9 @@
version=7.2.0
depends:
document_incoming
automation
ir
party
xml:
document.xml
view/document_incoming_list.xml

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='name']" position="before">
<field name="created_at" expand="2"/>
</xpath>
<xpath expr="/tree/field[@name='result']" position="after">
<field name="result_notes" expand="2"/>
</xpath>
</data>

View File

@@ -24,8 +24,14 @@ class Lot(ModelSQL, ModelView):
lot_name = fields.Char("Lot")
number = fields.Char("Number", readonly=True)
lot_qt = fields.Float("Quantity",required=False)
lot_unit = fields.Many2One('product.uom', "Unit",required=False)
lot_qt = fields.Float("Quantity", required=False,
states={
'readonly': Eval('lot_type') == 'virtual',
})
lot_unit = fields.Many2One('product.uom', "Unit", required=False,
states={
'readonly': Eval('lot_type') == 'virtual',
})
lot_product = fields.Many2One('product.product', "Product")
lot_type = fields.Selection([
('virtual', 'Open'),
@@ -486,8 +492,7 @@ class Lot(ModelSQL, ModelView):
st = state_id
else:
st = self.lot_state.id
logger.info("GET_HIST_QT:%s",st)
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
qt = round(lot.quantity,5)
gross_qt = round(lot.gross_quantity,5)
return qt, gross_qt

View File

@@ -36,7 +36,7 @@
<notebook colspan="4">
<page string="General" id="general">
<field name="lot_childs" colspan="4" mode="tree,form" view_ids="lot.lot_view_tree_sequence2,lot.lot_view_form"/>
<field name="lot_hist" colspan="4"/>
<field name="lot_hist" colspan="4" readonly="1"/>
</page>
<page string="Accounting" id="accounting">
<field name="pivot" widget="html_viewer" height="600" colspan="4"/>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0"?>
<form col="4">
<label name="lot"/>
<field name="lot"/>
<field name="lot" readonly="1"/>
<label name="quantity_type"/>
<field name="quantity_type"/>
<field name="quantity_type" readonly="1"/>
<label name="quantity"/>
<field name="quantity"/>
<field name="quantity" readonly="1"/>
<label name="gross_quantity"/>
<field name="gross_quantity"/>
<field name="gross_quantity" readonly="1"/>
</form>

View File

@@ -1,4 +1,4 @@
<tree editable="1">
<tree>
<field name="lot"/>
<field name="quantity_type"/>
<field name="quantity"/>

View File

@@ -1,4 +1,4 @@
<tree editable="1">
<tree>
<field name="lot"/>
<field name="quantity_type"/>
<field name="quantity"/>

View File

@@ -3,9 +3,6 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_price_value_unique">
<field name="text">Only one price value is allowed for each price index and price date.</field>
</record>
<record model="ir.message" id="msg_party_code_unique">
<field name="text">The code on party must be unique.</field>
</record>

View File

@@ -36,30 +36,51 @@ class Price(
('composite', 'Composite'),
], 'Index type')
price_type = fields.Many2One('price.fixtype', "Fixation type")
price_unit = fields.Many2One('product.uom', "Unit")
price_currency = fields.Many2One('currency.currency', "Currency")
price_area = fields.Many2One('price.area',"Market area")
price_calendar = fields.Many2One('price.calendar',"Calendar")
price_values = fields.One2Many('price.price_value', 'price', "Prices Values")
price_composite = fields.One2Many('price.composite','price',"Composites")
price_product = fields.One2Many('price.product', 'price', "Product")
price_unit = fields.Many2One('product.uom', "Unit")
price_currency = fields.Many2One('currency.currency', "Currency")
enable_linked_currency = fields.Boolean("Linked currency")
linked_currency = fields.Many2One('currency.linked', "Linked currency",
domain=[
('currency', '=', Eval('price_currency')),
],
states={
'invisible': ~Eval('enable_linked_currency'),
'required': Eval('enable_linked_currency'),
}, depends=['enable_linked_currency', 'price_currency'])
price_area = fields.Many2One('price.area',"Market area")
price_calendar = fields.Many2One('price.calendar',"Calendar")
price_values = fields.One2Many('price.price_value', 'price', "Prices Values")
price_composite = fields.One2Many('price.composite','price',"Composites")
price_product = fields.One2Many('price.product', 'price', "Product")
price_ct_size = fields.Numeric("Ct size")
def get_qt(self,nb_ct,unit):
Uom = Pool().get('product.uom')
return round(Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit)),4)
def get_price_per_qt(self,price,unit,currency):
price_qt = float(0)
Uom = Pool().get('product.uom')
Currency = Pool().get('currency.currency')
if currency != self.price_currency:
rates = Currency._get_rate([self.price_currency])
if rates[self.price_currency.id]:
price_qt = float(price) * Uom.compute_qty(unit, float(1), self.price_unit) * float(rates[self.price_currency.id])
else:
price_qt = float(price) * Uom.compute_qty(unit, float(1), self.price_unit)
return round(price_qt,4)
def get_qt(self,nb_ct,unit):
Uom = Pool().get('product.uom')
return round(Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit)),4)
def _price_in_main_currency(self, price):
price = Decimal(str(price or 0))
if self.enable_linked_currency and self.linked_currency:
factor = Decimal(self.linked_currency.factor or 0)
if factor:
price *= factor
return price
def get_price_per_qt(self,price,unit,currency):
price_qt = Decimal(0)
Uom = Pool().get('product.uom')
Currency = Pool().get('currency.currency')
price = self._price_in_main_currency(price)
unit_factor = Decimal(str(
Uom.compute_qty(unit, float(1), self.price_unit) or 0))
if currency != self.price_currency:
rates = Currency._get_rate([self.price_currency])
if rates[self.price_currency.id]:
price_qt = price * unit_factor * Decimal(str(
rates[self.price_currency.id]))
else:
price_qt = price * unit_factor
return round(price_qt,4)
def get_amount_nb_ct(self,price,nb_ct,unit,currency):
amount = Decimal(0)
@@ -68,17 +89,22 @@ class Price(
amount = Decimal(self.get_price_per_qt(price,unit,currency)) * Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit))
return round(amount,4)
def get_price(self,dt,unit,currency,last=False):
price = float(0)
PV = Pool().get('price.price_value')
if self.price_values:
dt = dt.strftime("%Y-%m-%d")
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
if not pv and last:
pv = PV.search([('price','=',self.id)],order=[('price_date', 'DESC')])
if pv:
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
return round(price,4)
def get_price(self,dt,unit,currency,last=False, relative_last=False):
price = float(0)
PV = Pool().get('price.price_value')
if self.price_values:
dt = dt.strftime("%Y-%m-%d")
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
if not pv and last:
pv = PV.search([('price','=',self.id)],order=[('price_date', 'DESC')])
elif not pv and relative_last:
pv = PV.search([
('price','=',self.id),
('price_date','<=',dt),
], order=[('price_date', 'DESC')])
if pv:
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
return round(price,4)
class FixType(ModelSQL,ModelView):
"Fixation type"

View File

@@ -9,7 +9,7 @@ from trytond.i18n import gettext
from trytond.model import (
DeactivableMixin, Index, ModelSQL, ModelView, MultiValueMixin, Unique,
ValueMixin, convert_from, fields, sequence_ordered)
from trytond.model.exceptions import AccessError, ValidationError
from trytond.model.exceptions import AccessError
from trytond.pool import Pool
from trytond.pyson import Bool, Eval
from trytond.tools import is_full_text, lstrip_wildcard
@@ -35,37 +35,16 @@ class PriceValue(
}), 'get_price_index')
price_date = fields.Date("Price date")
price_value = fields.Float("Price value")
open_price = fields.Float("Open price")
low_price = fields.Float("Low price")
price_value = fields.Float("Price value")
open_price = fields.Float("Open price")
low_price = fields.Float("Low price")
mid_price = fields.Float("Mid price")
high_price = fields.Float("High price")
@classmethod
def validate(cls, price_values):
super().validate(price_values)
cls.check_unique_price_date(price_values)
@classmethod
def check_unique_price_date(cls, price_values):
domains = []
for price_value in price_values:
if not price_value.price or not price_value.price_date:
continue
domain = [
('price', '=', price_value.price.id),
('price_date', '=', price_value.price_date),
]
if price_value.id:
domain.append(('id', '!=', price_value.id))
domains.append(['AND'] + domain)
if domains and cls.search(['OR'] + domains, limit=1):
raise ValidationError(
gettext('price.msg_price_value_unique'))
def get_price_index(self, name):
if self.price:
return self.price.price_index
return None
def get_price_index(self, name):
if self.price:
return self.price.price_index
return None
class PriceValueReport(
ModelSQL, ModelView):
@@ -77,9 +56,10 @@ class PriceValueReport(
r_price_desc = fields.Char("Description")
r_price_date = fields.Date("Price date")
r_price_value = fields.Float("Price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_high_price = fields.Float("High price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_mid_price = fields.Float("Mid price")
r_high_price = fields.Float("High price")
@classmethod
def table_query(cls):
@@ -108,10 +88,11 @@ class PriceValueReport(
pp.price_desc.as_('r_price_desc'),
pv.price_date.as_('r_price_date'),
pv.price_value.as_('r_price_value'),
pv.open_price.as_('r_open_price'),
pv.low_price.as_('r_low_price'),
pv.high_price.as_('r_high_price'),
where=wh)
pv.open_price.as_('r_open_price'),
pv.low_price.as_('r_low_price'),
pv.mid_price.as_('r_mid_price'),
pv.high_price.as_('r_high_price'),
where=wh)
return query
@@ -138,9 +119,10 @@ class PriceValueReport2(
r_price_desc = fields.Char("Description")
r_price_date = fields.Date("Price date")
r_price_value = fields.Float("Price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_high_price = fields.Float("High price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_mid_price = fields.Float("Mid price")
r_high_price = fields.Float("High price")
r_naphta_fob_low = fields.Float("Naph F Med l", digits=(1,2), help="Naphta Fob PAAAI00 low")
r_naphta_fob_high = fields.Float("Naph F Med h", digits=(1,2), help="Naphta Fob PAAAI00 high")
r_naphta_fob_mid = fields.Float("Naph F Med m", digits=(1,2), help="Naphta Fob PAAAI00 mid")
@@ -193,9 +175,10 @@ class PriceValueReport2(
Max(pp.price_desc).as_('r_price_desc'),
pv.price_date.as_('r_price_date'),
Max(pv.price_value).as_('r_price_value'),
Max(pv.open_price).as_('r_open_price'),
Max(pv.low_price).as_('r_low_price'),
Max(pv.high_price).as_('r_high_price'),
Max(pv.open_price).as_('r_open_price'),
Max(pv.low_price).as_('r_low_price'),
Max(pv.mid_price).as_('r_mid_price'),
Max(pv.high_price).as_('r_high_price'),
Max(Case((pv.price==15, pv.low_price),else_=0)).as_('r_naphta_fob_low'),
Max(Case((pv.price==15, pv.high_price),else_=0)).as_('r_naphta_fob_high'),
Max(Case((pv.price==15, (pv.high_price-pv.low_price)/2 + pv.low_price),else_=0)).as_('r_naphta_fob_mid'),

View File

@@ -12,10 +12,14 @@
<field name="price_type"/>
<label name="price_unit"/>
<field name="price_unit"/>
<label name="price_currency"/>
<field name="price_currency"/>
<label name="price_area"/>
<field name="price_area"/>
<label name="price_currency"/>
<field name="price_currency"/>
<label name="enable_linked_currency"/>
<field name="enable_linked_currency"/>
<label name="linked_currency"/>
<field name="linked_currency"/>
<label name="price_area"/>
<field name="price_area"/>
<label name="price_calendar"/>
<field name="price_calendar"/>
<label name="price_ct_size"/>

View File

@@ -2,8 +2,9 @@
<tree>
<field name="r_price_index"/>
<field name="r_price_desc"/>
<field name="r_price_date"/>
<field name="r_price_value"/>
<field name="r_low_price"/>
<field name="r_high_price"/>
</tree>
<field name="r_price_date"/>
<field name="r_price_value"/>
<field name="r_low_price"/>
<field name="r_mid_price"/>
<field name="r_high_price"/>
</tree>

View File

@@ -8,10 +8,12 @@
<label name="price_value"/>
<field name="price_value"/>
<label name="open_price"/>
<field name="open_price"/>
<label name="low_price"/>
<field name="low_price"/>
<label name="high_price"/>
<field name="high_price"/>
<field name="open_price"/>
<label name="low_price"/>
<field name="low_price"/>
<label name="mid_price"/>
<field name="mid_price"/>
<label name="high_price"/>
<field name="high_price"/>
</group>
</form>

View File

@@ -9,6 +9,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="price_value" fill="1" empty="0"/>
<field name="open_price" fill="1" empty="0"/>
<field name="low_price" fill="1" empty="0"/>
<field name="mid_price" fill="1" empty="0"/>
<field name="high_price" fill="1" empty="0"/>
</y>
</graph>

View File

@@ -2,8 +2,9 @@
<tree>
<field name="price_index"/>
<field name="price_date"/>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="high_price"/>
</tree>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="mid_price"/>
<field name="high_price"/>
</tree>

View File

@@ -2,8 +2,9 @@
<tree editable="1">
<field name="price_index"/>
<field name="price_date"/>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="high_price"/>
</tree>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="mid_price"/>
<field name="high_price"/>
</tree>

View File

@@ -519,6 +519,8 @@ class Purchase(
@property
def taxable_lines(self):
Date = Pool().get('ir.date')
tax_date = getattr(self, 'purchase_date', None) or Date.today()
taxable_lines = []
# In case we're called from an on_change we have to use some sensible
# defaults
@@ -529,7 +531,7 @@ class Purchase(
getattr(line, 'taxes', None) or [],
getattr(line, 'unit_price', None) or Decimal(0),
getattr(line, 'quantity', None) or 0,
None,
tax_date,
))
return taxable_lines
@@ -953,19 +955,22 @@ class Purchase(
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
@set_employee('confirmed_by')
# @Workflow.transition('confirmed')
# @set_employee('confirmed_by')
def confirm(cls, purchases):
pool = Pool()
Line = pool.get('purchase.line')
transaction = Transaction()
context = transaction.context
cls.set_purchase_date(purchases)
cls.write(purchases, {'state': 'confirmed'})
lines = list(sum((p.lines for p in purchases), ()))
Line._validate(lines, ['unit_price'])
# Confirm is intentionally disabled: clicking the button must not move
# the purchase to Confirmed or trigger downstream processing.
pass
# pool = Pool()
# Line = pool.get('purchase.line')
# cls.set_purchase_date(purchases)
#
# cls.write(purchases, {'state': 'confirmed'})
# lines = list(sum((p.lines for p in purchases), ()))
# Line._validate(lines, ['unit_price'])
#
# transaction = Transaction()
# context = transaction.context
# cls.store_cache(purchases)
# for process_after, sub_purchases in groupby(
# purchases, lambda p: p.process_after):
@@ -1013,16 +1018,22 @@ class Purchase(
for purchase in purchases:
logger.info("PROCESS_INVOICE:%s",action)
invoice = purchase.create_invoice(lots,action)
if not invoice:
logger.info(
"PROCESS_INVOICE_NO_INVOICE: purchase=%s action=%s",
getattr(purchase, 'id', None), action)
continue
if action == 'prov':
invoice.reference = 'Provisional'
elif action == 'service':
invoice.reference = 'Service'
else:
invoice.reference = 'Final'
if invoice:
invoices[purchase] = invoice
invoices[purchase] = invoice
cls._save_invoice(invoices,prepayment)
if invoices:
cls._save_invoice(invoices,prepayment)
return invoices
@classmethod
def _save_invoice(cls, invoices,prepayment=None):
@@ -1851,6 +1862,83 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
def on_change_with_currency(self, name=None):
return self.purchase.currency if self.purchase else None
@staticmethod
def _record_id(record):
return getattr(record, 'id', record)
@classmethod
def _get_last_fee_invoice_line(cls, fee, lot):
fee_id = cls._record_id(fee)
lot_id = cls._record_id(lot)
if not fee_id or not lot_id:
return
InvoiceLine = Pool().get('account.invoice.line')
lines = InvoiceLine.search([
('fee', '=', fee_id),
('lot', '=', lot_id),
('quantity', '>', 0),
('invoice.state', '!=', 'cancelled'),
], order=[
('invoice.invoice_date', 'DESC'),
('invoice.id', 'DESC'),
('id', 'DESC'),
], limit=1)
return lines[0] if lines else None
@staticmethod
def _fee_invoice_line_changed(previous_line, invoice_line):
if not previous_line:
return True
previous_quantity = Decimal(str(previous_line.quantity or 0))
quantity = Decimal(str(invoice_line.quantity or 0))
previous_price = Decimal(str(previous_line.unit_price or 0))
price = Decimal(str(invoice_line.unit_price or 0))
return previous_quantity != quantity or previous_price != price
@classmethod
def _get_fee_reversal_invoice_line(cls, previous_line, origin):
if not previous_line:
return
InvoiceLine = Pool().get('account.invoice.line')
reversal_line, = InvoiceLine.copy([previous_line], default={
'invoice': None,
'quantity': -previous_line.quantity,
'unit_price': previous_line.unit_price,
'party': previous_line.invoice.party,
'origin': str(origin),
})
return reversal_line
def _get_service_fee_invoice_lines(self, invoice_line, lot):
Fee = Pool().get('fee.fee')
fee = Fee.search(['purchase','=',self.purchase.id])
if not fee:
return [invoice_line]
fee = fee[0]
invoice_line.fee = fee
fee.warn_percent_price_partial_lots()
if fee.mode == 'lumpsum':
invoice_line.quantity = 1
elif fee.mode == 'pprice':
invoice_line.quantity = 1
elif fee.mode == 'ppack':
invoice_line.quantity = fee.quantity
else:
invoice_line.quantity = fee.get_fee_lots_qt()
if getattr(fee, 'state', None) != 'invoiced':
return [invoice_line]
previous_line = self._get_last_fee_invoice_line(fee, lot)
if not self._fee_invoice_line_changed(previous_line, invoice_line):
return []
reversal_line = self._get_fee_reversal_invoice_line(
previous_line, self)
if reversal_line:
return [reversal_line, invoice_line]
return [invoice_line]
def get_invoice_line(self,lots=None,action=None):
'Return a list of invoice line for purchase line'
pool = Pool()
@@ -1930,21 +2018,9 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
invoice_line.unit_price = self.unit_price
invoice_line.product = self.product
invoice_line.stock_moves = []
Fee = Pool().get('fee.fee')
fee = Fee.search(['purchase','=',self.purchase.id])
if fee:
invoice_line.fee = fee[0]
if fee[0].mode == 'lumpsum':
invoice_line.quantity = 1
elif fee[0].mode == 'ppack':
invoice_line.quantity = fee[0].quantity
else:
state_id = 0
LotQtType = Pool().get('lot.qt.type')
lqt = LotQtType.search([('name','=','BL')])
if lqt:
state_id = lqt[0].id
invoice_line.quantity = fee[0].get_fee_lots_qt(state_id)
lines.extend(
self._get_service_fee_invoice_lines(invoice_line, l))
continue
lines.append(invoice_line)
logger.info("GETINVLINE:%s",self.product.type)

View File

@@ -91,26 +91,10 @@ this repository contains the full copyright notices and license terms. -->
<field name="view" ref="purchase_view_form"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_service">
<field name="name">Services</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('line_type', '=', 'service')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="['AND',('state', '=', 'draft'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_quotation">
<field name="name">Validation</field>
<field name="sequence" eval="20"/>
<field name="name">Validated</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="['AND',('state', '=', 'quotation'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
@@ -118,7 +102,7 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_confirmed">
<field name="name">Confirmed</field>
<field name="sequence" eval="30"/>
<field name="sequence" eval="20"/>
<field name="domain" eval="['AND',('state', '=', 'confirmed'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
@@ -126,15 +110,31 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_processing">
<field name="name">Processing</field>
<field name="sequence" eval="40"/>
<field name="sequence" eval="30"/>
<field name="domain" eval="['AND',('state', '=', 'processing'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="40"/>
<field name="domain" eval="['AND',('state', '=', 'draft'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_service">
<field name="name">Services</field>
<field name="sequence" eval="50"/>
<field name="domain" eval="[('line_type', '=', 'service')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_exception">
<field name="name">Exception</field>
<field name="sequence" eval="50"/>
<field name="sequence" eval="60"/>
<field name="domain"
eval="['OR', ('invoice_state', '=', 'exception'), ('shipment_state', '=', 'exception')]"
pyson="1"/>
@@ -153,7 +153,6 @@ this repository contains the full copyright notices and license terms. -->
action="act_purchase_form"
sequence="10"
id="menu_purchase_form"/>
<record model="ir.action.act_window" id="act_purchase_invoice_relate">
<field name="name">Purchases</field>
<field name="res_model">purchase.purchase</field>

View File

@@ -85,7 +85,7 @@ this repository contains the full copyright notices and license terms. -->
<button name="handle_invoice_exception" icon="tryton-forward"/>
<button name="handle_shipment_exception" icon="tryton-forward"/>
<button name="confirm" icon="tryton-ok"/>
<button name="process"/>
<button name="process" invisible="1"/>
<button name="manual_invoice" icon="tryton-forward"/>
</group>
<field name="party_lang" invisible="1" colspan="6"/>

View File

@@ -2,18 +2,4 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='warehouse']" position="after">
<label name="carrier"/>
<field name="carrier"/>
</xpath>
<xpath expr="//page[@id='other']" position="before">
<page id="costs" string="Costs">
<label name="cost_used"/>
<group id="cost_used" col="-1" colspan="3">
<field name="cost_used" xexpand="0"/>
<field name="cost_currency_used"/>
<field name="cost_edit" xexpand="0" xalign="0"/>
</group>
</page>
</xpath>
</data>

View File

@@ -0,0 +1,254 @@
# AGENTS.md - Module `purchase_trade`
Ce guide complete le `AGENTS.md` racine.
Pour ce module, les regles locales ci-dessous priment.
## 1) Perimetre metier
Le module `purchase_trade` etend les flux achat/vente Tryton avec une logique
de negoce physique:
- contrats d'achat (`purchase.purchase`, `purchase.line`)
- contrats de vente (`sale.sale`, `sale.line`)
- lots physiques et virtuels
- matching achat/vente
- shipments et execution logistique
- frais (`fee.fee`)
- templates de documents metier et facture
## 2) Fichiers pivots
- Contrats achat:
- `modules/purchase_trade/purchase.py`
- Contrats vente:
- `modules/purchase_trade/sale.py`
- Lots / matching / invoicing:
- `modules/purchase_trade/lot.py`
- Shipments / lien facture-lot:
- `modules/purchase_trade/stock.py`
- Fees:
- `modules/purchase_trade/fee.py`
- Bridge facture / templates:
- `modules/purchase_trade/invoice.py`
- Vues:
- `modules/purchase_trade/view/*.xml`
- Actions module:
- `modules/purchase_trade/*.xml`
- Manifest:
- `modules/purchase_trade/tryton.cfg`
## 3) Documentation locale a lire en priorite
- Regles metier:
- `modules/purchase_trade/docs/business-rules.md`
- Documentation business publiee dans le wiki:
- `modules/purchase_trade/docs/business/*.md`
- Sources de verite de la documentation business generee:
- `modules/purchase_trade/docs_source/business/*.md`
- Regles templates:
- `modules/purchase_trade/docs/template-rules.md`
- Catalogue des proprietes templates:
- `modules/purchase_trade/docs/template-properties.md`
- Padding facture provisoire vente / validation comptable:
- `modules/purchase_trade/docs/padding-invoice-accounting.md`
## 4) Invariants metier a preserver
- Un lot `virtual` est la reference d'ouverture de quantite pour une `purchase.line`.
- Une `sale.line` doit aussi avoir au minimum un lot `virtual`; une valuation
cote sale ne doit donc pas disparaitre juste parce que le lot est `open`.
- Le lot physique est le pont principal entre:
- `purchase.line`
- `sale.line`
- shipment
- facture
- Pour remonter d'une facture vers shipment / BL / controller / fret:
- privilegier le lot physique
- ne pas multiplier des chemins d'acces concurrents
- Pour les champs de colis (`NB BALES`) dans les templates facture:
- la source de verite est `line.lot.lot_qt`
- sur une facture, sommer les `lot_qt` des lignes de facture
- tenir compte du signe de la ligne de facture pour les notes finales
- ne pas proratiser depuis le poids (`net` / `gross`)
- Le `FREIGHT VALUE` d'un template facture vient du `fee.fee` du shipment
dont le produit est `Maritime freight`.
- Pour `stock/insurance.fodt`, le `Amount insured` doit venir en priorite de
`110%` du total des `incoming_moves` (fallback fee `Insurance` si aucun
montant incoming calculable).
- Pour le surveyor du certificat d'assurance shipment, la priorite est:
`shipment.surveyor` -> `shipment.controller` -> fournisseur du fee
`Insurance`.
- Pour `payment_order.fodt`, utiliser des proprietes
`invoice.report_payment_order_*` plutot que des tokens legacy `<...>`.
- Ajouter un champ de template dans `Document Templates` ne rend pas le report
visible dans la fiche: il faut aussi l'action `ir.action.report` +
`ir.action.keyword` (`form_print`) cote `account.invoice`.
- Le wizard `Create contracts` en mode `matched` peut maintenant partir de
plusieurs `lot.qt`, mais doit conserver un matching par lot source et laisser
`created_by_code = True` sur les lignes creees pour ne pas declencher les
creations automatiques de lots dans les validations.
- En valuation / PnL:
- la valeur stockee dans `type` est la cle technique (`pur. priced`,
`sale priced`, `pur. fee`, etc.), pas le label affiche dans l'UI
- les references doivent rester coherentes avec le type de lot:
`Purchase/Open`, `Purchase/Physic`, `Sale/Open`, `Sale/Physic`
- pour une sale matchee, les lignes de valuation purchase generees sur un lot
physique doivent aussi renseigner `sale` et `sale_line` afin de remonter
dans l'onglet PnL de la sale
- une sale non matchee doit etre valorisable "sale-first" et alimenter
`valuation.valuation` / `valuation.valuation.line`
- si une `sale.line` `basis` n'a ni `price_summary` ni `lot_price_sale`,
creer quand meme une ligne `sale priced` avec `price = 0` et `amount = 0`
plutot que de ne rien generer
- le MTM ne doit etre renseigne que pour `pur. priced`, `sale priced` et
`derivative`; jamais pour les fees
- `mtm_price` doit afficher le prix brut de valorisation (sans ratio), alors
que `mtm` reste le montant calcule selon la logique de strategie
- En pricing:
- le `unit_price` doit rester un prix de base, hors `premium`
- le `premium` doit impacter le prix total economique et donc le `amount`,
aussi bien en `priced` qu'en `basis`
- dans `pricing.pricing` en saisie manuelle, l'utilisateur renseigne
seulement `quantity` et `settl_price`
- `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et
`eod_price` sont des valeurs derivees et ne doivent pas etre saisies a la
main
- en manuel, `fixed_qt` = cumul des `quantity` du groupe trie par
`pricing_date`
- en manuel, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`
- en manuel, `unfixed_qt_price` = `settl_price` de la ligne
- pour les documents commerciaux / facture, une ligne `basis` affiche le
`premium` comme prix visible, pas le prix economique total
- si `linked currency` est active, le `premium` est saisi dans la devise /
unite liee (ex: `USC/LB`) puis converti vers le repere de la ligne pour le
calcul du `amount`
- en `basis + linked currency`, le `linked_price` doit representer le prix
basis brut (hors premium) dans la devise liee; le `unit_price` reste ce
prix brut converti, et le `premium` converti est ajoute seulement dans
l'`amount`
- si `linked currency` est cochee, `linked_price`, `linked_currency` et
`linked_unit` sont requis
- dans les forms, presenter le bloc prix dans l'ordre:
`price_type` -> linked fields -> `premium` -> `unit_price` -> `amount`
- en valuation `basis`, le premium s'applique a chaque composant, pas
uniquement a une ligne de resume
- pour une ligne `basis` sans `price_summary`, la valuation fallback doit
utiliser `unit_price + premium` (et pas `unit_price` seul)
- a la validation d'une `sale.line`, si un lot virtuel est cree et qu'aucun
matching purchase n'existe, il faut lancer `generate_from_sale_line()` pour
alimenter le PnL sale-first
- En padding de facture provisoire vente:
- le padding est saisi globalement dans `lot.invoice`
- il est uniquement applique aux factures provisoires cote vente
- il est reparti par lot et stocke dans `lot.sale_invoice_padding`
- la ligne facture affiche `Inc. padding` depuis le lot
- le padding augmente `account.invoice.line.quantity`, donc le move principal
de facture inclut deja le montant padding
- les comptes de padding viennent de `account.configuration`:
`Default Sale Padding` et `Default Accrual Padding`
- la section `Padding` doit rester avant la section `Invoice` dans
`Financial / Configuration`
- la provisoire cree un `additional_move` debit `Default Sale Padding` /
credit `Default Accrual Padding`
- la finale cree l'inverse en reprenant le montant depuis
`lot.sale_invoice_line_prov`, donc avec le prix, la devise, la date et le
taux de la provisoire
- le wizard final doit calculer son delta depuis la quantite provisoire hors
padding: `sale_invoice_line_prov.quantity - sale_invoice_padding`
- En modification de quantite contractuelle:
- `quantity_theorical` est la source contractuelle; `quantity` peut refleter
l'execute physique.
- le `lot.qt` libre doit etre resynchronise sur le solde ouvert reel:
`quantity_theorical - lots physiques - lot.qt deja matches/shippes`.
- ne jamais ajuster le `lot.qt` libre uniquement par delta si des lots
physiques existent deja.
- les fees suivent les lots effectifs: physiques s'il en existe dans
`fee.lots`, sinon virtuel.
- conserver le lien `fee.lots` vers le virtuel comme fallback; ne pas le
supprimer lors de la creation d'un physique.
- le PnL fee applique la meme regle: full open tant qu'il n'y a que le
virtuel, puis uniquement les physiques.
- detail durable:
`modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021.
- En execution controller / SLA:
- les objectifs de repartition controller utilisent `party.execution.area`
(`country.region`).
- les couts SLA utilisent `party.execution.place` et peuvent matcher par
`country`, par `location`, ou par couple `country + location`.
- la creation du fee controller depuis shipment part de
`shipment.to_location`; le pays de matching est
`shipment.to_location.country`.
- priorite cout SLA: couple pays+location, puis location seule, puis pays
seul.
## 5) Conventions de modification
### Documentation business
- Ne pas modifier directement une page generee sous
`modules/purchase_trade/docs/business/` si elle contient le commentaire
`Generated from docs_source/business`.
- Toute regle business nouvelle ou modifiee doit etre editee dans
`modules/purchase_trade/docs_source/business/`.
- Apres modification des sources business, regenerer le wiki avec:
`python modules/purchase_trade/docs/tools/render_business_docs.py`
- Avant de rendre une modification documentaire, verifier que le rendu publie
est synchronise avec:
`python modules/purchase_trade/docs/tools/render_business_docs.py --check`
- Les pages francaises et anglaises miroir doivent rester synchronisees.
- Le rendu publie doit rester lisible dans MkDocs meme sans extensions
optionnelles: eviter les marqueurs bruts `!!!` et `:material-...:` dans les
fichiers publies.
1. Modifier la logique metier dans le fichier pivot le plus proche.
2. Si un template `.fodt` devient complexe, deplacer la logique dans une
propriete Python `report_*`.
3. Pour une facture trade, preferer enrichir `modules/purchase_trade/invoice.py`
plutot que surcharger lourdement le `.fodt`.
4. Si une regle metier durable change, mettre a jour
`docs/business-rules.md`.
5. Si une convention de template change, mettre a jour
`docs/template-rules.md`.
6. Pour les vues XML Tryton de ce module, utiliser `editable="1"` sur les
`<tree>` editables; ne pas utiliser `editable="bottom"`.
7. Ne pas ajouter `keyword_open` / `open_keyword` sur les vues `<tree>`:
ces attributs ne sont pas supportes par Tryton.
8. Si une regle de texte par defaut durable est demandee sur achat/vente,
preferer un singleton de configuration expose dans un menu fonctionnel
existant plutot qu'un menu technique `purchase_trade`.
## 6) Pieges connus
- Plusieurs actions de report `account.invoice` peuvent sembler rendre le meme
document a cause du cache `invoice_report_cache`.
- Les reports alternatifs (`Final Invoice`, `Prepayment`, etc.) ne doivent pas
reutiliser le cache du report standard sans verification.
- Pour les donnees achat/vente partagees, ne pas supposer qu'une facture de
vente doit lire directement sur la `sale.line`: souvent, la verite metier
passe par le lot physique et/ou la `account.invoice.line`.
- Les templates `invoice_ict*` peuvent partager les memes proprietes Python;
si une regle doit valoir pour provisional et final, la mettre dans
`modules/purchase_trade/invoice.py` plutot que dupliquer dans les `.fodt`.
- Dans les ecrans PnL, le label `Sale price` correspond au type stocke
`sale priced`; idem pour `Pur. price` / `pur. priced`.
- Une ligne `basis` sans resume de pricing peut sinon disparaitre de la
valuation si aucun fallback explicite a `0` n'est prevu.
- Le calcul du prix peut diverger entre `unit_price`, `linked_price`,
`lot_price` et valuation si le premium n'est pas traite explicitement dans
chaque maillon.
- Sur `account.invoice`, le workflow `Validate` doit maintenant aligner
fournisseur et client pour:
- creation du `account.move`
- attribution du `number`
- `Post` ne doit pas reintroduire une difference de session/fresh login cote
client
- Pour le padding provisoire vente, ne pas changer la quantite physique du lot:
l'ecart doit rester porte par `lot.sale_invoice_padding` et visible sur la
facture via `Inc. padding`.
## 7) Definition of done (module `purchase_trade`)
- Le flux achat/vente/lot cible reste coherent.
- Les impacts templates/facture ont ete verifies conceptuellement.
- Les docs locales ont ete mises a jour si une nouvelle regle durable a emerge.
- Le patch reste minimal et local au domaine demande.

View File

@@ -5,9 +5,11 @@ from trytond.pool import Pool
from . import (
account,
configuration,
purchase,
sale,
global_reporting,
ctrm_reporting,
stock,
derivative,
lot,
@@ -32,9 +34,11 @@ from . import (
dimension,
weight_report,
backtoback,
service,
invoice,
)
service,
invoice,
duplicate,
coffee,
)
def register():
Pool.register(
@@ -55,16 +59,76 @@ def register():
lc.LCMT700,
lc.LCMessage,
lc.CreateLCStart,
global_reporting.GRConfiguration,
global_reporting.GRConfiguration,
ctrm_reporting.CTRMPhysicalPosition,
ctrm_reporting.CTRMPhysicalPositionContext,
ctrm_reporting.CTRMFinancialPosition,
ctrm_reporting.CTRMFinancialPositionContext,
ctrm_reporting.CTRMNetPosition,
ctrm_reporting.CTRMNetPositionContext,
ctrm_reporting.CTRMOpenPosition,
ctrm_reporting.CTRMOpenPositionContext,
ctrm_reporting.CTRMLongShortProductStrategy,
ctrm_reporting.CTRMLongShortCommodityDirection,
ctrm_reporting.CTRMLongShortPeriodProduct,
ctrm_reporting.CTRMLongShortPricingStatus,
ctrm_reporting.CTRMLongShortDetail,
ctrm_reporting.CTRMPositionExposure,
ctrm_reporting.CTRMPositionExposureContext,
ctrm_reporting.CTRMRealizedPnl,
ctrm_reporting.CTRMRealizedPnlContext,
ctrm_reporting.CTRMMtmPnl,
ctrm_reporting.CTRMMtmPnlContext,
ctrm_reporting.CTRMPnlExplain,
ctrm_reporting.CTRMPnlExplainContext,
ctrm_reporting.CTRMPnlDimension,
ctrm_reporting.CTRMPnlDimensionContext,
ctrm_reporting.CTRMCreditRisk,
ctrm_reporting.CTRMCreditRiskContext,
ctrm_reporting.CTRMShippingLogistics,
ctrm_reporting.CTRMShippingLogisticsContext,
ctrm_reporting.CTRMInventory,
ctrm_reporting.CTRMInventoryContext,
ctrm_reporting.CTRMContractPerformance,
ctrm_reporting.CTRMContractPerformanceContext,
ctrm_reporting.CTRMScheduling,
ctrm_reporting.CTRMSchedulingContext,
ctrm_reporting.CTRMAccruals,
ctrm_reporting.CTRMAccrualsContext,
ctrm_reporting.CTRMSettlements,
ctrm_reporting.CTRMSettlementsContext,
ctrm_reporting.CTRMCashFlow,
ctrm_reporting.CTRMCashFlowContext,
ctrm_reporting.CTRMGLReconciliation,
ctrm_reporting.CTRMGLReconciliationContext,
configuration.Configuration,
pricing.ImportPricesStart,
pricing.ImportPricesResult,
coffee.CoffeeSample,
coffee.CoffeeCuppingCriterion,
coffee.CoffeeCuppingSession,
coffee.CoffeeCuppingSessionSample,
coffee.CoffeeCuppingCup,
coffee.CoffeeCuppingResult,
coffee.CoffeeCuppingResultLine,
coffee.CoffeeLabAnalysis,
coffee.PurchaseLine,
coffee.SaleLine,
duplicate.TradeCustomDuplicateStart,
module='purchase_trade', type_='model')
Pool.register(
configuration.PurchaseConfiguration,
module='purchase', type_='model')
Pool.register(
configuration.SaleConfiguration,
module='sale', type_='model')
Pool.register(
incoming.ImportSwift,
incoming.PrepareDocuments,
incoming.AnalyzeConditions,
lc.CreateLCWizard,
pricing.ImportPrices,
duplicate.TradeCustomDuplicate,
module='purchase_trade', type_='wizard'
)
Pool.register(
@@ -86,6 +150,15 @@ def register():
party.PartyExecutionPlace,
payment_term.PaymentTerm,
payment_term.PaymentTermLine,
stock.CharterRateType,
stock.CharterConditionRate,
stock.CharterConditionLaytimeStartRule,
stock.CharterCondition,
stock.CharterParty,
purchase.ContractClause,
purchase.ContractTemplate,
purchase.ContractTemplateLine,
purchase.ContractClauseSelection,
purchase.Purchase,
purchase.Line,
purchase.Estimated,
@@ -104,6 +177,7 @@ def register():
open_position.OpenPositionContext,
optional.OptionalScenario,
fee.Fee,
fee.FeeRule,
fee.FeeLots,
purchase.FeeLots,
valuation.Valuation,
@@ -111,6 +185,9 @@ def register():
valuation.ValuationDyn,
valuation.ValuationReport,
valuation.ValuationReportContext,
valuation.ValuationProcessDimension,
valuation.ValuationProcessStart,
valuation.ValuationProcessResult,
derivative.Derivative,
derivative.DerivativeMatch,
derivative.MatchWizardStart,
@@ -118,16 +195,18 @@ def register():
derivative.DerivativeReportContext,
fee.FeeReport,
fee.FeeContext,
forex.ForexCoverPhysicalContract,
forex.PForex,
forex.ForexBI,
purchase.PnlBI,
purchase.PositionBI,
stock.Move,
forex.ForexCoverPhysicalContract,
forex.PForex,
forex.ForexBI,
purchase.PurchasePnlGraph,
purchase.PnlBI,
purchase.PositionBI,
stock.Move,
stock.Location,
stock.InvoiceLine,
stock.ShipmentIn,
stock.ShipmentWR,
stock.ShipmentIn,
stock.ShipmentControllerEmailStart,
stock.ShipmentWR,
stock.ShipmentInternal,
stock.ShipmentOut,
stock.StatementOfFacts,
@@ -135,16 +214,19 @@ def register():
stock.ImportSoFStart,
stock.RevaluateStart,
stock.Account,
stock.AccountMoveLine,
stock.AccountMove,
stock.ContainerType,
stock.ShipmentContainer,
lot.Lot,
lot.LotQt,
lot.LotReport,
lot.LotContext,
lot.LotShippingStart,
lot.LotMatchingStart,
stock.AccountMoveLine,
stock.AccountMove,
stock.ContainerType,
stock.ShipmentContainer,
lot.Lot,
lot.LotQt,
lot.LotReport,
lot.LotContext,
lot.LotShippingStart,
lot.LotPlanTransportStart,
lot.LotPlanTransportLine,
lot.LotMarkFinishedStart,
lot.LotMatchingStart,
lot.LotWeighingStart,
lot.LotAddLot,
lot.LotInvoicingLot,
@@ -154,10 +236,12 @@ def register():
lot.LotImportLot,
lot.LotInvoiceStart,
lot.LotMatchingLot,
lot.LotWeighingLot,
lot.ContractsStart,
lot.ContractDetail,
lot.LotFCR,
lot.LotWeighingLot,
lot.ContractsStart,
lot.ContractDetail,
lot.MirrorStart,
lot.MirrorLine,
lot.LotFCR,
lot.LotMove,
lot.LotAccountingGraph,
workflow.ExecutionPlan,
@@ -173,17 +257,18 @@ def register():
purchase.DocTypeTemplate,
purchase.PurchaseStrategy,
purchase.PriceComposition,
purchase.PremiumComposition,
purchase.QualityAnalysis,
purchase.Assay,
purchase.AssayLine,
purchase.AssayElement,
purchase.AssayUnit,
purchase.PayableRule,
purchase.PenaltyRule,
purchase.PenaltyRuleTier,
purchase.ConcentrateTerm,
backtoback.Backtoback,
dimension.AnalyticDimension,
purchase.PenaltyRule,
purchase.PenaltyRuleTier,
purchase.ConcentrateTerm,
backtoback.Backtoback,
dimension.AnalyticDimension,
dimension.AnalyticDimensionValue,
dimension.AnalyticDimensionAssignment,
weight_report.WeightReport,
@@ -191,6 +276,13 @@ def register():
Pool.register(
account.PhysicalTradeIFRS,
module='purchase_trade', type_='model')
Pool.register(
configuration.AccountConfiguration,
configuration.AccountConfigurationDefaultAccount,
invoice.InvoiceLineLotWeight,
invoice.InvoicePaddingReport,
invoice.InvoicePaddingContext,
module='purchase_trade', type_='model')
Pool.register(
invoice.Invoice,
invoice.InvoiceLine,
@@ -216,6 +308,8 @@ def register():
purchase.PurchaseCrop,
module='lot', type_='model')
Pool.register(
sale.ContractClauseSelection,
sale.CharterCondition,
sale.Sale,
sale.SaleLine,
sale.SaleCreatePurchaseInput,
@@ -238,37 +332,56 @@ def register():
sale.OpenPosition,
sale.Backtoback,
sale.AnalyticDimensionAssignment,
sale.PriceComposition,
module='sale', type_='model')
sale.PriceComposition,
sale.PremiumComposition,
module='sale', type_='model')
Pool.register(
lot.LotShipping,
lot.LotMatching,
lot.LotShipping,
lot.LotPlanTransport,
lot.LotMatching,
lot.LotGoMatching,
#lot.LotMatchingUnit,
lot.LotWeighing,
lot.CreateContracts,
lot.LotUnmatch,
lot.LotUnship,
lot.LotWeighing,
lot.CreateContracts,
lot.CreateMirror,
lot.LotUnmatch,
lot.LotMarkFinished,
lot.LotUnship,
lot.LotRemove,
lot.LotInvoice,
lot.LotAdding,
lot.LotImporting,
stock.FindVessel,
lot.LotAdding,
lot.LotImporting,
stock.ShipmentControllerEmail,
stock.FindVessel,
stock.SofUpdate,
stock.Revaluate,
purchase.GoToBi,
purchase_prepayment.CreatePrepaymentWizard,
purchase.PurchaseAllocationsWizard,
purchase.InvoicePayment,
stock.ImportSoFWizard,
dashboard.BotWizard,
dashboard.DashboardLoader,
forex.ForexReport,
purchase.PnlReport,
stock.ImportSoFWizard,
dashboard.BotWizard,
dashboard.DashboardLoader,
forex.ForexReport,
purchase.PnlReport,
purchase.PositionReport,
valuation.ValuationProcess,
derivative.DerivativeMatchWizard,
module='purchase', type_='wizard')
Pool.register(
sale.SaleCreatePurchase,
sale.SaleAllocationsWizard,
module='sale', type_='wizard')
Pool.register(
invoice.InvoiceReport,
invoice.SaleReport,
invoice.PurchaseReport,
stock.ShipmentShippingReport,
stock.ShipmentInsuranceReport,
stock.ShipmentCOOReport,
stock.ShipmentPackingListReport,
stock.ShipmentLinkageReport,
stock.LotReportLinkageReport,
stock.LotReportLinkageFinalReport,
module='purchase_trade', type_='report')

View File

@@ -1,9 +1,10 @@
# account.py
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import PoolMeta
from trytond.pyson import Eval
__all__ = ['PhysicalTradeIFRS']
__all__ = [
'PhysicalTradeIFRS',
]
__metaclass__ = PoolMeta

View File

@@ -1,6 +1,12 @@
<?xml version="1.0"?>
<tryton>
<data>
<record model="ir.ui.view" id="account_configuration_view_form">
<field name="model">account.configuration</field>
<field name="inherit" ref="account.configuration_view_form"/>
<field name="name">account_configuration_form</field>
</record>
<record model="res.group" id="group_physical_trade_ifrs">
<field name="name">Physical Trade IFRS</field>
</record>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,395 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="coffee_sample_view_tree">
<field name="model">coffee.sample</field>
<field name="type">tree</field>
<field name="name">coffee_sample_tree</field>
</record>
<record model="ir.ui.view" id="coffee_sample_view_form">
<field name="model">coffee.sample</field>
<field name="type">form</field>
<field name="name">coffee_sample_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_criterion_view_tree">
<field name="model">coffee.cupping.criterion</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_criterion_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_criterion_view_form">
<field name="model">coffee.cupping.criterion</field>
<field name="type">form</field>
<field name="name">coffee_cupping_criterion_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_view_tree">
<field name="model">coffee.cupping.session</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_session_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_view_form">
<field name="model">coffee.cupping.session</field>
<field name="type">form</field>
<field name="name">coffee_cupping_session_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_sample_view_tree">
<field name="model">coffee.cupping.session.sample</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_session_sample_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_sample_view_form">
<field name="model">coffee.cupping.session.sample</field>
<field name="type">form</field>
<field name="name">coffee_cupping_session_sample_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_cup_view_tree">
<field name="model">coffee.cupping.cup</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_cup_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_cup_view_form">
<field name="model">coffee.cupping.cup</field>
<field name="type">form</field>
<field name="name">coffee_cupping_cup_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_view_tree">
<field name="model">coffee.cupping.result</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_result_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_view_form">
<field name="model">coffee.cupping.result</field>
<field name="type">form</field>
<field name="name">coffee_cupping_result_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_line_view_tree">
<field name="model">coffee.cupping.result.line</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_result_line_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_line_view_form">
<field name="model">coffee.cupping.result.line</field>
<field name="type">form</field>
<field name="name">coffee_cupping_result_line_form</field>
</record>
<record model="ir.ui.view" id="coffee_lab_analysis_view_tree">
<field name="model">coffee.lab.analysis</field>
<field name="type">tree</field>
<field name="name">coffee_lab_analysis_tree</field>
</record>
<record model="ir.ui.view" id="coffee_lab_analysis_view_form">
<field name="model">coffee.lab.analysis</field>
<field name="type">form</field>
<field name="name">coffee_lab_analysis_form</field>
</record>
<record model="ir.action.act_window" id="act_coffee_sample_form">
<field name="name">Coffee Samples</field>
<field name="res_model">coffee.sample</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_sample_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_sample_view_tree"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_sample_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_sample_view_form"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_pending">
<field name="name">Pending</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', 'in', ['requested', 'received', 'sent_to_lab', 'under_review'])]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_expiring">
<field name="name">Expiring soon</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('expires_soon', '=', True)]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_expired">
<field name="name">Expired</field>
<field name="sequence" eval="30"/>
<field name="domain" eval="[('is_expired', '=', True)]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_approved">
<field name="name">Approved</field>
<field name="sequence" eval="40"/>
<field name="domain" eval="[('state', '=', 'approved')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window" id="act_coffee_cupping_session_form">
<field name="name">Cupping Sessions</field>
<field name="res_model">coffee.cupping.session</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_session_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_cupping_session_view_tree"/>
<field name="act_window" ref="act_coffee_cupping_session_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_session_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_cupping_session_view_form"/>
<field name="act_window" ref="act_coffee_cupping_session_form"/>
</record>
<record model="ir.action.act_window" id="act_coffee_cupping_criterion_form">
<field name="name">Cupping Criteria</field>
<field name="res_model">coffee.cupping.criterion</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_criterion_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_cupping_criterion_view_tree"/>
<field name="act_window" ref="act_coffee_cupping_criterion_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_criterion_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_cupping_criterion_view_form"/>
<field name="act_window" ref="act_coffee_cupping_criterion_form"/>
</record>
<record model="ir.action.act_window" id="act_coffee_lab_analysis_form">
<field name="name">Lab Analyses</field>
<field name="res_model">coffee.lab.analysis</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_lab_analysis_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_lab_analysis_view_tree"/>
<field name="act_window" ref="act_coffee_lab_analysis_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_lab_analysis_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_lab_analysis_view_form"/>
<field name="act_window" ref="act_coffee_lab_analysis_form"/>
</record>
<menuitem name="Coffee" parent="purchase.menu_purchase" sequence="18"
id="menu_coffee_purchase"/>
<menuitem name="Samples" parent="menu_coffee_purchase" sequence="10"
action="act_coffee_sample_form" id="menu_coffee_sample_purchase"/>
<menuitem name="Lab Analyses" parent="menu_coffee_purchase"
sequence="15" action="act_coffee_lab_analysis_form"
id="menu_coffee_lab_analysis_purchase"/>
<menuitem name="Cupping Sessions" parent="menu_coffee_purchase"
sequence="20" action="act_coffee_cupping_session_form"
id="menu_coffee_cupping_session_purchase"/>
<menuitem name="Cupping Criteria" parent="menu_coffee_purchase"
sequence="30" action="act_coffee_cupping_criterion_form"
id="menu_coffee_cupping_criterion_purchase"/>
<menuitem name="Coffee" parent="sale.menu_sale" sequence="18"
id="menu_coffee_sale"/>
<menuitem name="Samples" parent="menu_coffee_sale" sequence="10"
action="act_coffee_sample_form" id="menu_coffee_sample_sale"/>
<menuitem name="Lab Analyses" parent="menu_coffee_sale"
sequence="15" action="act_coffee_lab_analysis_form"
id="menu_coffee_lab_analysis_sale"/>
<menuitem name="Cupping Sessions" parent="menu_coffee_sale"
sequence="20" action="act_coffee_cupping_session_form"
id="menu_coffee_cupping_session_sale"/>
<menuitem name="Cupping Criteria" parent="menu_coffee_sale"
sequence="30" action="act_coffee_cupping_criterion_form"
id="menu_coffee_cupping_criterion_sale"/>
<record model="ir.ui.menu" id="menu_coffee_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_sample_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_lab_analysis_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_session_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_criterion_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_sample_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_lab_analysis_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_session_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_criterion_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.model.button" id="coffee_sample_receive_button">
<field name="model">coffee.sample</field>
<field name="name">receive</field>
<field name="string">Receive</field>
</record>
<record model="ir.model.button" id="coffee_sample_send_to_lab_button">
<field name="model">coffee.sample</field>
<field name="name">send_to_lab</field>
<field name="string">Send to lab</field>
</record>
<record model="ir.model.button" id="coffee_sample_review_button">
<field name="model">coffee.sample</field>
<field name="name">review</field>
<field name="string">Review</field>
</record>
<record model="ir.model.button" id="coffee_sample_approve_button">
<field name="model">coffee.sample</field>
<field name="name">approve</field>
<field name="string">Approve</field>
</record>
<record model="ir.model.button" id="coffee_sample_reject_button">
<field name="model">coffee.sample</field>
<field name="name">reject</field>
<field name="string">Reject</field>
</record>
<record model="ir.model.button" id="coffee_sample_evaluate_quality_button">
<field name="model">coffee.sample</field>
<field name="name">evaluate_quality</field>
<field name="string">Evaluate quality</field>
</record>
<record model="ir.model.button" id="coffee_sample_expire_button">
<field name="model">coffee.sample</field>
<field name="name">expire</field>
<field name="string">Expire</field>
</record>
<record model="ir.model.button" id="coffee_sample_archive_button">
<field name="model">coffee.sample</field>
<field name="name">archive</field>
<field name="string">Archive</field>
</record>
<record model="ir.model.button" id="coffee_sample_reset_to_requested_button">
<field name="model">coffee.sample</field>
<field name="name">reset_to_requested</field>
<field name="string">Reset</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_prepare_button">
<field name="model">coffee.cupping.session</field>
<field name="name">prepare</field>
<field name="string">Prepare</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_start_button">
<field name="model">coffee.cupping.session</field>
<field name="name">start</field>
<field name="string">Start</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_done_button">
<field name="model">coffee.cupping.session</field>
<field name="name">done</field>
<field name="string">Done</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_cancel_button">
<field name="model">coffee.cupping.session</field>
<field name="name">cancel</field>
<field name="string">Cancel</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_draft_button">
<field name="model">coffee.cupping.session</field>
<field name="name">draft</field>
<field name="string">Draft</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_send_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">send</field>
<field name="string">Send</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_receive_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">receive</field>
<field name="string">Receive</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_accept_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">accept</field>
<field name="string">Accept</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_reject_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">reject</field>
<field name="string">Reject</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_cancel_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">cancel</field>
<field name="string">Cancel</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_draft_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">draft</field>
<field name="string">Draft</field>
</record>
<record model="ir.model.access" id="access_coffee_sample">
<field name="model">coffee.sample</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_criterion">
<field name="model">coffee.cupping.criterion</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_session">
<field name="model">coffee.cupping.session</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_session_sample">
<field name="model">coffee.cupping.session.sample</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_cup">
<field name="model">coffee.cupping.cup</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_result">
<field name="model">coffee.cupping.result</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_result_line">
<field name="model">coffee.cupping.result.line</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_lab_analysis">
<field name="model">coffee.lab.analysis</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,154 @@
from trytond.pool import Pool
from trytond.transaction import Transaction
import datetime
ITSA_COMPANY_NAME = 'ITSA'
def record_id(record):
return getattr(record, 'id', record)
def is_itsa_company(company=None):
if company is None:
company_id = Transaction().context.get('company')
if not company_id:
return False
Company = Pool().get('company.company')
company = Company(company_id)
party = getattr(company, 'party', None)
return bool(party and party.name == ITSA_COMPANY_NAME)
def _search_first(model_name, domains):
Model = Pool().get(model_name)
for domain in domains:
try:
records = Model.search(domain, limit=1)
except Exception:
continue
if records:
return records[0]
def default_itsa_currency():
currency = _search_first('currency.currency', [
[('code', '=', 'USD')],
[('name', '=', 'USD')],
[('symbol', '=', 'USD')],
])
return record_id(currency) if currency else None
def default_itsa_price_fix_type():
fix_type = _search_first('price.fixtype', [
[('name', '=', 'Market Price')],
[('name', '=', 'Market price')],
[('name', 'ilike', 'Market Price')],
])
return record_id(fix_type) if fix_type else None
def default_itsa_price_calendar():
calendar = _search_first('price.calendar', [
[('name', '=', 'Argus Sulphuric Acid')],
[('name', 'ilike', 'Argus Sulphuric Acid')],
])
return record_id(calendar) if calendar else None
def default_itsa_unit():
unit = _search_first('product.uom', [
[('symbol', '=', 'Mt')],
[('name', '=', 'Mt')],
[('symbol', '=', 'MT')],
[('name', '=', 'MT')],
])
return record_id(unit) if unit else None
def default_itsa_bulk_unit():
unit = _search_first('product.uom', [
[('symbol', '=', 'Bulk')],
[('name', '=', 'Bulk')],
[('symbol', '=', 'bulk')],
[('name', '=', 'bulk')],
])
return record_id(unit) if unit else None
def _company_from_values(values):
company = values.get('company')
if company and hasattr(company, 'party'):
return company
company_id = record_id(company) or Transaction().context.get('company')
if not company_id:
return None
return Pool().get('company.company')(company_id)
def _dimension(name):
return _search_first('analytic.dimension', [
[('code', '=', name)],
[('name', '=', name)],
[('code', 'ilike', '%%%s%%' % name)],
[('name', 'ilike', '%%%s%%' % name)],
])
def _book_year_suffix(today=None):
if today is None:
today = Pool().get('ir.date').today()
year = today.year
if today < datetime.date(year, 4, 1):
year -= 1
return '%02d' % (year % 100)
def _book_value(dimension, today=None):
if not dimension:
return None
suffix = _book_year_suffix(today)
return _search_first('analytic.dimension.value', [
[
('dimension', '=', record_id(dimension)),
('code', 'ilike', '%%%s%%' % suffix),
],
[
('dimension', '=', record_id(dimension)),
('name', 'ilike', '%%%s%%' % suffix),
],
])
def default_itsa_analytic_dimension_assignments(company=None, today=None):
if not is_itsa_company(company):
return []
assignments = []
book = _dimension('Book')
if book:
value = _book_value(book, today=today)
assignments.append({
'dimension': record_id(book),
'value': record_id(value) if value else None,
})
strategy = _dimension('Strategy')
if strategy:
assignments.append({
'dimension': record_id(strategy),
'value': None,
})
return assignments
def add_itsa_analytic_dimension_defaults(values):
if values.get('analytic_dimensions'):
return
company = _company_from_values(values)
assignments = default_itsa_analytic_dimension_assignments(company=company)
if assignments:
values['analytic_dimensions'] = [('create', assignments)]

View File

@@ -0,0 +1,292 @@
from sql import Table
from sql.operators import Exists
from trytond.model import ModelSingleton, ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
class PurchaseConfiguration(metaclass=PoolMeta):
__name__ = 'purchase.configuration'
allow_modification_after_validation = fields.Boolean(
"Autorise modification after validation")
auto_hedging = fields.Boolean("Auto hedge")
auto_hedging_over = fields.Boolean("Over hedge")
@classmethod
def default_allow_modification_after_validation(cls):
return False
@classmethod
def default_auto_hedging(cls):
return False
@classmethod
def default_auto_hedging_over(cls):
return False
class SaleConfiguration(metaclass=PoolMeta):
__name__ = 'sale.configuration'
allow_modification_after_validation = fields.Boolean(
"Autorise modification after validation")
auto_hedging = fields.Boolean("Auto hedge")
auto_hedging_over = fields.Boolean("Over hedge")
@classmethod
def default_allow_modification_after_validation(cls):
return False
@classmethod
def default_auto_hedging(cls):
return False
@classmethod
def default_auto_hedging_over(cls):
return False
class AccountConfiguration(metaclass=PoolMeta):
__name__ = 'account.configuration'
default_sale_padding_account = fields.MultiValue(fields.Many2One(
'account.account', "Default Sale Padding",
domain=[
('closed', '!=', True),
('type.revenue', '=', True),
('company', '=', Eval('context', {}).get('company', -1)),
]))
default_accrual_padding_account = fields.MultiValue(fields.Many2One(
'account.account', "Default Accrual Padding",
domain=[
('closed', '!=', True),
('type.statement', '=', 'balance'),
('company', '=', Eval('context', {}).get('company', -1)),
]))
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {
'default_sale_padding_account',
'default_accrual_padding_account',
}:
return pool.get('account.configuration.default_account')
return super().multivalue_model(field)
class AccountConfigurationDefaultAccount(metaclass=PoolMeta):
__name__ = 'account.configuration.default_account'
default_sale_padding_account = fields.Many2One(
'account.account', "Default Sale Padding",
domain=[
('closed', '!=', True),
('type.revenue', '=', True),
('company', '=', Eval('company', -1)),
])
default_accrual_padding_account = fields.Many2One(
'account.account', "Default Accrual Padding",
domain=[
('closed', '!=', True),
('type.statement', '=', 'balance'),
('company', '=', Eval('company', -1)),
])
class Configuration(ModelSingleton, ModelSQL, ModelView):
"Purchase Trade Configuration"
__name__ = 'purchase_trade.configuration'
_REPORT_LABELS = (
('sale_report_label', 'sale', 'report_sale', 'Proforma'),
('sale_commission_report_label', 'account_invoice',
'report_sale_commission_ict', 'Commission invoice Sale'),
('sale_bill_report_label', 'sale', 'report_bill', 'Draft'),
('invoice_report_label', 'account_invoice', 'report_invoice',
'Invoice'),
('invoice_cndn_report_label', 'account_invoice',
'report_invoice_ict_final', 'CN/DN'),
('invoice_commission_cndn_report_label', 'account_invoice',
'report_invoice_ict_commission_final', 'CN/DN Commission'),
('invoice_prepayment_report_label', 'account_invoice',
'report_prepayment', 'Prepayment'),
('invoice_packing_list_report_label', 'purchase_trade',
'report_invoice_packing_list', 'Packing List'),
('invoice_payment_order_report_label', 'purchase_trade',
'report_payment_order', 'Payment Order'),
('purchase_report_label', 'purchase', 'report_purchase', 'Purchase'),
('purchase_commission_report_label', 'account_invoice',
'report_purchase_commission_ict', 'Commission invoice Purchase'),
('shipment_shipping_report_label', 'stock',
'report_shipment_in_shipping', 'Shipping instructions'),
('shipment_insurance_report_label', 'purchase_trade',
'report_shipment_in_insurance', 'Insurance'),
('shipment_coo_report_label', 'purchase_trade',
'report_shipment_in_coo', 'COO'),
('shipment_packing_list_report_label', 'purchase_trade',
'report_shipment_in_packing_list', 'Packing List'),
('shipment_linkage_report_label', 'purchase_trade',
'report_shipment_in_linkage', 'Linkage'),
)
_COFFEE_MENU_XML_IDS = (
'menu_coffee_purchase',
'menu_coffee_sample_purchase',
'menu_coffee_lab_analysis_purchase',
'menu_coffee_cupping_session_purchase',
'menu_coffee_cupping_criterion_purchase',
'menu_coffee_sale',
'menu_coffee_sample_sale',
'menu_coffee_lab_analysis_sale',
'menu_coffee_cupping_session_sale',
'menu_coffee_cupping_criterion_sale',
)
_ORPHAN_ACT_WINDOW_XML_IDS = (
'act_itsa_operations_workflow',
'act_tradon_processes',
)
pricing_rule = fields.Text("Pricing Rule")
active_coffee_compatibility = fields.Boolean(
"Active coffee compatibility")
sale_report_template = fields.Char("Sale Template")
sale_report_label = fields.Char("Sale Menu Label")
sale_commission_report_template = fields.Char(
"Sale Commission Template")
sale_commission_report_label = fields.Char(
"Sale Commission Menu Label")
sale_bill_report_template = fields.Char("Sale Bill Template")
sale_bill_report_label = fields.Char("Sale Bill Menu Label")
sale_final_report_template = fields.Char("Sale Final Template")
invoice_report_template = fields.Char("Invoice Template")
invoice_report_label = fields.Char("Invoice Menu Label")
invoice_cndn_report_template = fields.Char("CN/DN Template")
invoice_cndn_report_label = fields.Char("CN/DN Menu Label")
invoice_commission_cndn_report_template = fields.Char(
"CN/DN Commission Template")
invoice_commission_cndn_report_label = fields.Char(
"CN/DN Commission Menu Label")
invoice_prepayment_report_template = fields.Char("Prepayment Template")
invoice_prepayment_report_label = fields.Char("Prepayment Menu Label")
invoice_packing_list_report_template = fields.Char("Packing List Template")
invoice_packing_list_report_label = fields.Char("Packing List Menu Label")
invoice_payment_order_report_template = fields.Char("Payment Order Template")
invoice_payment_order_report_label = fields.Char(
"Payment Order Menu Label")
purchase_report_template = fields.Char("Purchase Template")
purchase_report_label = fields.Char("Purchase Menu Label")
purchase_commission_report_template = fields.Char(
"Purchase Commission Template")
purchase_commission_report_label = fields.Char(
"Purchase Commission Menu Label")
shipment_shipping_report_template = fields.Char("Shipping Template")
shipment_shipping_report_label = fields.Char("Shipping Menu Label")
shipment_insurance_report_template = fields.Char("Insurance Template")
shipment_insurance_report_label = fields.Char("Insurance Menu Label")
shipment_coo_report_template = fields.Char("COO Template")
shipment_coo_report_label = fields.Char("COO Menu Label")
shipment_packing_list_report_template = fields.Char("Packing List Template")
shipment_packing_list_report_label = fields.Char(
"Packing List Menu Label")
shipment_linkage_report_template = fields.Char("Linkage Template")
shipment_linkage_report_label = fields.Char("Linkage Menu Label")
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
if module_name == 'purchase_trade':
cls._cleanup_orphan_act_window_xml_ids()
@classmethod
def _cleanup_orphan_act_window_xml_ids(cls):
cursor = Transaction().connection.cursor()
model_data = Table('ir_model_data')
act_window = Table('ir_action_act_window')
cursor.execute(*model_data.delete(
where=(model_data.module == 'purchase_trade')
& (model_data.model == 'ir.action.act_window')
& model_data.fs_id.in_(cls._ORPHAN_ACT_WINDOW_XML_IDS)
& ~Exists(act_window.select(
act_window.id,
where=act_window.id == model_data.db_id))))
@classmethod
def default_active_coffee_compatibility(cls):
return False
@classmethod
def create(cls, vlist):
records = super().create(vlist)
cls._sync_report_labels(records)
active = any(
bool(values.get(
'active_coffee_compatibility',
cls.default_active_coffee_compatibility()))
for values in vlist)
cls._sync_coffee_menu_visibility(records, active=active)
cls._clear_coffee_view_cache()
return records
@classmethod
def write(cls, *args):
actions = list(zip(args[::2], args[1::2]))
active = None
for records, values in actions:
if 'active_coffee_compatibility' in values:
active = bool(values['active_coffee_compatibility'])
super().write(*args)
records = sum(args[::2], [])
cls._sync_report_labels(records)
cls._sync_coffee_menu_visibility(records, active=active)
if active is not None:
cls._clear_coffee_view_cache()
@classmethod
def _sync_report_labels(cls, records):
if not records:
return
pool = Pool()
ModelData = pool.get('ir.model.data')
ActionReport = pool.get('ir.action.report')
to_write = []
for record in records:
for field_name, module, xml_id, default_label in cls._REPORT_LABELS:
label = (getattr(record, field_name, '') or '').strip()
action_id = ModelData.get_id(module, xml_id)
action = ActionReport(action_id)
target_label = label or default_label
if getattr(action, 'name', '') != target_label:
to_write.extend(([action], {'name': target_label}))
if to_write:
with Transaction().set_user(0):
ActionReport.write(*to_write)
@classmethod
def _sync_coffee_menu_visibility(cls, records=None, active=None):
if active is None:
configurations = cls.search([], limit=1)
active = bool(
configurations
and configurations[0].active_coffee_compatibility)
pool = Pool()
ModelData = pool.get('ir.model.data')
Menu = pool.get('ir.ui.menu')
menus = []
for xml_id in cls._COFFEE_MENU_XML_IDS:
try:
menu_id = ModelData.get_id('purchase_trade', xml_id)
except Exception:
continue
menus.append(Menu(menu_id))
if menus:
with Transaction().set_user(0):
Menu.write(menus, {'active': active})
@classmethod
def _clear_coffee_view_cache(cls):
ModelView._fields_view_get_cache.clear()

View File

@@ -0,0 +1,72 @@
<tryton>
<data>
<record model="ir.ui.view" id="purchase_trade_configuration_view_form">
<field name="model">purchase_trade.configuration</field>
<field name="type">form</field>
<field name="name">configuration_form</field>
</record>
<record model="ir.ui.view" id="purchase_trade_template_configuration_view_form">
<field name="model">purchase_trade.configuration</field>
<field name="type">form</field>
<field name="name">template_configuration_form</field>
</record>
<record model="ir.ui.view" id="purchase_configuration_view_form">
<field name="model">purchase.configuration</field>
<field name="inherit" ref="purchase.purchase_configuration_view_form"/>
<field name="name">purchase_configuration_form</field>
</record>
<record model="ir.ui.view" id="sale_configuration_view_form">
<field name="model">sale.configuration</field>
<field name="inherit" ref="sale.sale_configuration_view_form"/>
<field name="name">sale_configuration_form</field>
</record>
<record model="ir.action.act_window" id="act_purchase_trade_configuration_form">
<field name="name">Trade Configuration</field>
<field name="res_model">purchase_trade.configuration</field>
</record>
<record model="ir.action.act_window.view" id="act_purchase_trade_configuration_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_trade_configuration_view_form"/>
<field name="act_window" ref="act_purchase_trade_configuration_form"/>
</record>
<record model="ir.action.act_window" id="act_purchase_trade_template_configuration_form">
<field name="name">Document Templates</field>
<field name="res_model">purchase_trade.configuration</field>
</record>
<record model="ir.action.act_window.view" id="act_purchase_trade_template_configuration_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_trade_template_configuration_view_form"/>
<field name="act_window" ref="act_purchase_trade_template_configuration_form"/>
</record>
<menuitem
name="Trade Configuration"
parent="price.menu_price"
action="act_purchase_trade_configuration_form"
sequence="10"
id="menu_purchase_trade_configuration"
icon="tryton-settings"/>
<menuitem
name="Trade Configuration"
parent="purchase.menu_configuration"
action="act_purchase_trade_configuration_form"
sequence="90"
id="menu_purchase_trade_configuration_purchase"
icon="tryton-settings"/>
<menuitem
name="Trade Configuration"
parent="sale.menu_configuration"
action="act_purchase_trade_configuration_form"
sequence="90"
id="menu_purchase_trade_configuration_sale"
icon="tryton-settings"/>
<menuitem
name="Document Templates"
parent="document_incoming.menu_configuration"
action="act_purchase_trade_template_configuration_form"
sequence="20"
id="menu_purchase_trade_template_configuration"
icon="tryton-settings"/>
</data>
</tryton>

File diff suppressed because it is too large Load Diff

View File

@@ -12,9 +12,15 @@ class Cron(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
cls.method.selection.append(
('forex.cron|update_forex', "Update Forex Prices"))
super().__setup__()
cls.method.selection.append(
('forex.cron|update_forex', "Update Forex Prices"))
cls.method.selection.append(
('valuation.valuation|update_daily_snapshot',
"Update Valuation Snapshot"))
cls.method.selection.append(
('pricing.pricing|update_daily_pricing',
"Update Pricing"))
class PriceCron(ModelSQL, ModelView):
"Price Cron"

View File

@@ -36,10 +36,22 @@
<field name="model" search="[('model', '=', 'forex.cron')]"/>
</record> -->
<record model="ir.cron" id="cron_cron">
<field name="method">forex.cron|update_forex</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
</data>
</tryton>
<record model="ir.cron" id="cron_cron">
<field name="method">forex.cron|update_forex</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
<record model="ir.cron" id="valuation_daily_snapshot_cron">
<field name="method">valuation.valuation|update_daily_snapshot</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
<record model="ir.cron" id="pricing_daily_update_cron">
<field name="method">pricing.pricing|update_daily_pricing</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
</data>
</tryton>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,486 @@
<tryton>
<data>
<record model="ir.ui.view" id="ctrm_position_physical_context_view_form">
<field name="model">ctrm.reporting.position.physical.context</field>
<field name="type">form</field>
<field name="name">ctrm_position_physical_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_position_physical_view_list">
<field name="model">ctrm.reporting.position.physical</field>
<field name="type">tree</field>
<field name="name">ctrm_position_physical_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_position_physical">
<field name="name">Physical Position</field>
<field name="res_model">ctrm.reporting.position.physical</field>
<field name="context_model">ctrm.reporting.position.physical.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_position_physical_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_position_physical_view_list"/>
<field name="act_window" ref="act_ctrm_position_physical"/>
</record>
<record model="ir.ui.view" id="ctrm_position_financial_context_view_form">
<field name="model">ctrm.reporting.position.financial.context</field>
<field name="type">form</field>
<field name="name">ctrm_position_financial_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_position_financial_view_list">
<field name="model">ctrm.reporting.position.financial</field>
<field name="type">tree</field>
<field name="name">ctrm_position_financial_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_position_financial">
<field name="name">Financial Position</field>
<field name="res_model">ctrm.reporting.position.financial</field>
<field name="context_model">ctrm.reporting.position.financial.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_position_financial_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_position_financial_view_list"/>
<field name="act_window" ref="act_ctrm_position_financial"/>
</record>
<record model="ir.ui.view" id="ctrm_position_net_context_view_form">
<field name="model">ctrm.reporting.position.net.context</field>
<field name="type">form</field>
<field name="name">ctrm_position_net_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_position_net_view_list">
<field name="model">ctrm.reporting.position.net</field>
<field name="type">tree</field>
<field name="name">ctrm_position_net_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_position_net">
<field name="name">Net Consolidated Position</field>
<field name="res_model">ctrm.reporting.position.net</field>
<field name="context_model">ctrm.reporting.position.net.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_position_net_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_position_net_view_list"/>
<field name="act_window" ref="act_ctrm_position_net"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_realized_context_view_form">
<field name="model">ctrm.reporting.pnl.realized.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_realized_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_realized_view_list">
<field name="model">ctrm.reporting.pnl.realized</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_realized_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_realized">
<field name="name">Realized P&amp;L</field>
<field name="res_model">ctrm.reporting.pnl.realized</field>
<field name="context_model">ctrm.reporting.pnl.realized.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_realized_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_realized_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_realized"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_mtm_context_view_form">
<field name="model">ctrm.reporting.pnl.mtm.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_mtm_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_mtm_view_list">
<field name="model">ctrm.reporting.pnl.mtm</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_mtm_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_mtm">
<field name="name">Mark-to-Market</field>
<field name="res_model">ctrm.reporting.pnl.mtm</field>
<field name="context_model">ctrm.reporting.pnl.mtm.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_mtm_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_mtm_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_mtm"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_explain_context_view_form">
<field name="model">ctrm.reporting.pnl.explain.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_explain_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_explain_view_list">
<field name="model">ctrm.reporting.pnl.explain</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_explain_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_explain">
<field name="name">P&amp;L Explain</field>
<field name="res_model">ctrm.reporting.pnl.explain</field>
<field name="context_model">ctrm.reporting.pnl.explain.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_explain_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_explain_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_explain"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_dimension_context_view_form">
<field name="model">ctrm.reporting.pnl.dimension.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_dimension_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_dimension_view_list">
<field name="model">ctrm.reporting.pnl.dimension</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_dimension_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_dimension">
<field name="name">P&amp;L by Dimension</field>
<field name="res_model">ctrm.reporting.pnl.dimension</field>
<field name="context_model">ctrm.reporting.pnl.dimension.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_dimension_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_dimension_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_dimension"/>
</record>
<record model="ir.ui.view" id="ctrm_credit_risk_context_view_form">
<field name="model">ctrm.reporting.risk.credit.context</field>
<field name="type">form</field>
<field name="name">ctrm_credit_risk_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_credit_risk_view_list">
<field name="model">ctrm.reporting.risk.credit</field>
<field name="type">tree</field>
<field name="name">ctrm_credit_risk_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_credit_risk">
<field name="name">Credit Risk</field>
<field name="res_model">ctrm.reporting.risk.credit</field>
<field name="context_model">ctrm.reporting.risk.credit.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_credit_risk_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_credit_risk_view_list"/>
<field name="act_window" ref="act_ctrm_credit_risk"/>
</record>
<record model="ir.ui.view" id="ctrm_shipping_logistics_context_view_form">
<field name="model">ctrm.reporting.operations.shipping.context</field>
<field name="type">form</field>
<field name="name">ctrm_shipping_logistics_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_shipping_logistics_view_list">
<field name="model">ctrm.reporting.operations.shipping</field>
<field name="type">tree</field>
<field name="name">ctrm_shipping_logistics_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_shipping_logistics">
<field name="name">Shipping Logistics</field>
<field name="res_model">ctrm.reporting.operations.shipping</field>
<field name="context_model">ctrm.reporting.operations.shipping.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_shipping_logistics_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_shipping_logistics_view_list"/>
<field name="act_window" ref="act_ctrm_shipping_logistics"/>
</record>
<record model="ir.ui.view" id="ctrm_inventory_context_view_form">
<field name="model">ctrm.reporting.operations.inventory.context</field>
<field name="type">form</field>
<field name="name">ctrm_inventory_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_inventory_view_list">
<field name="model">ctrm.reporting.operations.inventory</field>
<field name="type">tree</field>
<field name="name">ctrm_inventory_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_inventory">
<field name="name">Inventory</field>
<field name="res_model">ctrm.reporting.operations.inventory</field>
<field name="context_model">ctrm.reporting.operations.inventory.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_inventory_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_inventory_view_list"/>
<field name="act_window" ref="act_ctrm_inventory"/>
</record>
<record model="ir.ui.view" id="ctrm_contract_performance_context_view_form">
<field name="model">ctrm.reporting.operations.contract.context</field>
<field name="type">form</field>
<field name="name">ctrm_contract_performance_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_contract_performance_view_list">
<field name="model">ctrm.reporting.operations.contract</field>
<field name="type">tree</field>
<field name="name">ctrm_contract_performance_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_contract_performance">
<field name="name">Contract Performance</field>
<field name="res_model">ctrm.reporting.operations.contract</field>
<field name="context_model">ctrm.reporting.operations.contract.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_contract_performance_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_contract_performance_view_list"/>
<field name="act_window" ref="act_ctrm_contract_performance"/>
</record>
<record model="ir.ui.view" id="ctrm_scheduling_context_view_form">
<field name="model">ctrm.reporting.operations.scheduling.context</field>
<field name="type">form</field>
<field name="name">ctrm_scheduling_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_scheduling_view_list">
<field name="model">ctrm.reporting.operations.scheduling</field>
<field name="type">tree</field>
<field name="name">ctrm_scheduling_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_scheduling">
<field name="name">Scheduling</field>
<field name="res_model">ctrm.reporting.operations.scheduling</field>
<field name="context_model">ctrm.reporting.operations.scheduling.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_scheduling_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_scheduling_view_list"/>
<field name="act_window" ref="act_ctrm_scheduling"/>
</record>
<record model="ir.ui.view" id="ctrm_accruals_context_view_form">
<field name="model">ctrm.reporting.finance.accruals.context</field>
<field name="type">form</field>
<field name="name">ctrm_accruals_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_accruals_view_list">
<field name="model">ctrm.reporting.finance.accruals</field>
<field name="type">tree</field>
<field name="name">ctrm_accruals_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_accruals">
<field name="name">Accruals</field>
<field name="res_model">ctrm.reporting.finance.accruals</field>
<field name="context_model">ctrm.reporting.finance.accruals.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_accruals_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_accruals_view_list"/>
<field name="act_window" ref="act_ctrm_accruals"/>
</record>
<record model="ir.ui.view" id="ctrm_settlements_context_view_form">
<field name="model">ctrm.reporting.finance.settlements.context</field>
<field name="type">form</field>
<field name="name">ctrm_settlements_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_settlements_view_list">
<field name="model">ctrm.reporting.finance.settlements</field>
<field name="type">tree</field>
<field name="name">ctrm_settlements_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_settlements">
<field name="name">Settlements and Invoicing</field>
<field name="res_model">ctrm.reporting.finance.settlements</field>
<field name="context_model">ctrm.reporting.finance.settlements.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_settlements_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_settlements_view_list"/>
<field name="act_window" ref="act_ctrm_settlements"/>
</record>
<record model="ir.ui.view" id="ctrm_cashflow_context_view_form">
<field name="model">ctrm.reporting.finance.cashflow.context</field>
<field name="type">form</field>
<field name="name">ctrm_cashflow_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_cashflow_view_list">
<field name="model">ctrm.reporting.finance.cashflow</field>
<field name="type">tree</field>
<field name="name">ctrm_cashflow_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_cashflow">
<field name="name">Cash Flow Forecast</field>
<field name="res_model">ctrm.reporting.finance.cashflow</field>
<field name="context_model">ctrm.reporting.finance.cashflow.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_cashflow_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_cashflow_view_list"/>
<field name="act_window" ref="act_ctrm_cashflow"/>
</record>
<record model="ir.ui.view" id="ctrm_gl_reconciliation_context_view_form">
<field name="model">ctrm.reporting.finance.gl.context</field>
<field name="type">form</field>
<field name="name">ctrm_gl_reconciliation_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_gl_reconciliation_view_list">
<field name="model">ctrm.reporting.finance.gl</field>
<field name="type">tree</field>
<field name="name">ctrm_gl_reconciliation_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_gl_reconciliation">
<field name="name">GL Reconciliation</field>
<field name="res_model">ctrm.reporting.finance.gl</field>
<field name="context_model">ctrm.reporting.finance.gl.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_gl_reconciliation_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_gl_reconciliation_view_list"/>
<field name="act_window" ref="act_ctrm_gl_reconciliation"/>
</record>
<menuitem
name="Positions"
parent="purchase_trade.menu_global_reporting"
sequence="10"
id="menu_ctrm_positions"
icon="tradon-reporting-positions"/>
<menuitem
name="Physical Position"
parent="menu_ctrm_positions_physical_group"
sequence="10"
action="act_ctrm_position_physical"
id="menu_ctrm_position_physical"/>
<menuitem
name="Financial Position"
parent="menu_ctrm_positions_financial_group"
sequence="10"
action="act_ctrm_position_financial"
id="menu_ctrm_position_financial"/>
<menuitem
name="Net Consolidated Position"
parent="menu_ctrm_positions"
sequence="30"
action="act_ctrm_position_net"
id="menu_ctrm_position_net"/>
<menuitem
name="Hedge Coverage"
parent="menu_ctrm_positions"
sequence="40"
id="menu_ctrm_position_hedge"/>
<menuitem
name="P&amp;L"
parent="purchase_trade.menu_global_reporting"
sequence="20"
id="menu_ctrm_pnl"
icon="tradon-reporting-pnl"/>
<menuitem
name="Realized P&amp;L"
parent="menu_ctrm_pnl"
sequence="10"
action="act_ctrm_pnl_realized"
id="menu_ctrm_pnl_realized"/>
<menuitem
name="Mark-to-Market"
parent="menu_ctrm_pnl"
sequence="20"
action="act_ctrm_pnl_mtm"
id="menu_ctrm_pnl_mtm"/>
<menuitem
name="P&amp;L Explain"
parent="menu_ctrm_pnl"
sequence="30"
action="act_ctrm_pnl_explain"
id="menu_ctrm_pnl_explain"/>
<menuitem
name="P&amp;L by Dimension"
parent="menu_ctrm_pnl"
sequence="40"
action="act_ctrm_pnl_dimension"
id="menu_ctrm_pnl_dimension"/>
<menuitem
name="Risk"
parent="purchase_trade.menu_global_reporting"
sequence="30"
id="menu_ctrm_risk"
icon="tradon-reporting-risk"/>
<menuitem
name="VaR"
parent="menu_ctrm_risk"
sequence="10"
action="risk.act_pos_report_form"
id="menu_ctrm_risk_var"/>
<menuitem
name="Stress Tests"
parent="menu_ctrm_risk"
sequence="20"
id="menu_ctrm_risk_stress"/>
<menuitem
name="Sensitivities"
parent="menu_ctrm_risk"
sequence="30"
id="menu_ctrm_risk_sensitivities"/>
<menuitem
name="Limit Monitoring"
parent="menu_ctrm_risk"
sequence="40"
id="menu_ctrm_risk_limits"/>
<menuitem
name="Credit Risk"
parent="menu_ctrm_risk"
sequence="50"
action="act_ctrm_credit_risk"
id="menu_ctrm_risk_credit"/>
<menuitem
name="Operations &amp; Logistics"
parent="purchase_trade.menu_global_reporting"
sequence="40"
id="menu_ctrm_operations"
icon="tradon-reporting-operations"/>
<menuitem
name="Shipping Logistics"
parent="menu_ctrm_operations"
sequence="10"
action="act_ctrm_shipping_logistics"
id="menu_ctrm_operations_shipping"/>
<menuitem
name="Inventory"
parent="menu_ctrm_operations"
sequence="20"
action="act_ctrm_inventory"
id="menu_ctrm_operations_inventory"/>
<menuitem
name="Contract Performance"
parent="menu_ctrm_operations"
sequence="30"
action="act_ctrm_contract_performance"
id="menu_ctrm_operations_contract"/>
<menuitem
name="Scheduling"
parent="menu_ctrm_operations"
sequence="40"
action="act_ctrm_scheduling"
id="menu_ctrm_operations_scheduling"/>
<menuitem
name="Finance &amp; Accounting"
parent="purchase_trade.menu_global_reporting"
sequence="50"
id="menu_ctrm_finance"
icon="tradon-reporting-finance"/>
<menuitem
name="Accruals"
parent="menu_ctrm_finance"
sequence="10"
action="act_ctrm_accruals"
id="menu_ctrm_finance_accruals"/>
<menuitem
name="Hedge Accounting"
parent="menu_ctrm_finance"
sequence="20"
id="menu_ctrm_finance_hedge_accounting"/>
<menuitem
name="Settlements and Invoicing"
parent="menu_ctrm_finance"
sequence="30"
action="act_ctrm_settlements"
id="menu_ctrm_finance_settlements"/>
<menuitem
name="Cash Flow Forecast"
parent="menu_ctrm_finance"
sequence="40"
action="act_ctrm_cashflow"
id="menu_ctrm_finance_cash_flow"/>
<menuitem
name="GL Reconciliation"
parent="menu_ctrm_finance"
sequence="50"
action="act_ctrm_gl_reconciliation"
id="menu_ctrm_finance_gl_reconciliation"/>
</data>
</tryton>

View File

@@ -193,16 +193,29 @@ class Dashboard(ModelSQL, ModelView):
self.chatbot = 'chatbot:' + json.dumps(dial, ensure_ascii=False)
logger.info("EXITONCHANGE",self.chatbot)
@staticmethod
def _empty_fx_rates():
return (None, None, None, None, None, None, None, None, None, None)
@classmethod
def _find_currency_by_code(cls, code):
Currency = Pool().get('currency.currency')
currencies = Currency.search([('name', '=', code)], limit=1)
if not currencies:
currencies = Currency.search([('code', '=', code)], limit=1)
return currencies[0] if currencies else None
def get_last_five_fx_rates(self, from_code='USD', to_code='EUR'):
"""
Retourne (dernier_taux, avant_dernier_taux) pour le couple de devises.
"""
Currency = Pool().get('currency.currency')
CurrencyRate = Pool().get('currency.currency.rate')
# Récupérer les devises EUR et USD
from_currency = Currency.search([('name', '=', from_code)])[0]
to_currency = Currency.search([('name', '=', to_code)])[0]
from_currency = self._find_currency_by_code(from_code)
to_currency = self._find_currency_by_code(to_code)
if not from_currency or not to_currency:
return self._empty_fx_rates()
# Recherche des taux de la devise de base (ex: USD)
rates = CurrencyRate.search(
@@ -212,7 +225,7 @@ class Dashboard(ModelSQL, ModelView):
)
if not rates:
return None, None, None, None, None
return self._empty_fx_rates()
# Calcul du taux EUR/USD
# Si la devise principale de la société est EUR, et que le taux stocké est
@@ -232,7 +245,13 @@ class Dashboard(ModelSQL, ModelView):
# last_rate = 1 / last_rate if last_rate else None
# prev_rate = 1 / prev_rate if prev_rate else None
return round(1/f1,6), round(1/f2,6) if f2 else None, round(1/f3,6) if f3 else None, round(1/f4,6) if f4 else None, round(1/f5,6) if f5 else None, d1, d2, d3, d4, d5
return (
round(1/f1,6) if f1 else None,
round(1/f2,6) if f2 else None,
round(1/f3,6) if f3 else None,
round(1/f4,6) if f4 else None,
round(1/f5,6) if f5 else None,
d1, d2, d3, d4, d5)
def get_tremor(self,name):
Date = Pool().get('ir.date')
@@ -430,7 +449,7 @@ class Dashboard(ModelSQL, ModelView):
News = Pool().get('news.news')
Date = Pool().get('ir.date')
news_list = News.search([('active', '=', True)], limit=5, order=[('publish_date', 'DESC')])
last_rate,prev_rate, = self.get_last_five_fx_rates()
last_rate, prev_rate = self.get_last_five_fx_rates()[:2]
if last_rate and prev_rate:
variation = ((last_rate - prev_rate) / prev_rate) * 100 if prev_rate else 0
direction = "📈" if variation > 0 else "📉"
@@ -749,7 +768,11 @@ class BotWizard(Wizard):
lms.lot_r_id = ls.getLotQt()[0].id
lms.lot_matched_qt = lp.lot_quantity
lms.lot_sale = s
LotQt.match_lots([lmp],[lms])
Lot = Pool().get('lot.lot')
with Lot.skip_quantity_consistency():
LotQt.match_lots([lmp],[lms])
Lot.assert_lines_quantity_consistency(
[p.lines[0], s.lines[0]])
Sale._process_shipment([s])
elif do_ == "add":
@@ -876,4 +899,4 @@ class BotAction(ModelSQL, ModelView):
@classmethod
def state_dashboard(cls):
return 1
return 1

View File

@@ -65,7 +65,6 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView):
value = fields.Many2One(
'analytic.dimension.value',
'Value',
required=True,
domain=[
('dimension', '=', Eval('dimension')),
],
@@ -76,4 +75,4 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView):
'purchase.purchase',
'Purchase',
ondelete='CASCADE'
)
)

View File

@@ -1,94 +0,0 @@
# Backlog - Market Price Import
Scope: code changes related only to `BR-PT-004 - Market Price Import`.
## PT-BL-001 - Enforce uniqueness at model level
- Status: done
- Implementation commit: `eaae2e5`
- Priority: high
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: enforce uniqueness of `(price, price_date)` at model level without adding a database constraint yet.
- Work done:
- added model-level validation on `price.price_value`
- added Tryton message `price.msg_price_value_unique`
- added targeted tests for duplicate create and duplicate write validation
- Validation status:
- `git diff --check` passed
- automated Python tests still need to be run in a working Tryton Python environment
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- possibly `modules/price/price_value.py` if the validation belongs on the target model
- Expected tests:
- importing a row for an existing `(price, price_date)` skips or updates according to `Overwrite existing price`
- creating/editing duplicate `price.price_value` records at model level is rejected if validation is implemented on `price.price_value`
## PT-BL-002 - Use locale-aware slash date parsing
- Status: open
- Priority: medium
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: prefer the user's/default locale date format for ambiguous slash dates, then fall back to the current parsing order only if no locale preference is available.
- Current behavior: `DD/MM/YYYY` is tried before `MM/DD/YYYY`.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- slash dates follow the configured/default locale when available
- slash dates keep a deterministic fallback when locale is unavailable
- existing accepted formats still work
## PT-BL-003 - Reject missing price_value
- Status: open
- Priority: high
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: `price_value` is mandatory for imported rows. Missing `price_value` must be reported as a row error.
- Current behavior: empty `price_value` is imported as an empty value.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- missing `price_value` is reported in `errors`
- missing `high_price`, `low_price`, and `open_price` remain allowed
- valid `price_value` still imports and updates correctly
## PT-BL-004 - Detect duplicate rows inside the same Excel file
- Status: open
- Priority: high
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: duplicate rows for the same `(price_index, price_date)` inside the same Excel file must be reported as row errors. The duplicate row must not import or update data.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- duplicate `(price_index, price_date)` rows in the same import produce row errors
- first occurrence behavior remains unchanged
- duplicate rows do not overwrite previous imported or updated values
## PT-BL-005 - Categorize import result errors
- Status: open
- Priority: medium
- Source: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: distinguish business validation errors from file, parsing, and technical errors in the import result.
- Current behavior: row errors are grouped under one `Errors` section.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- `modules/purchase_trade/view/import_prices_result_form.xml` only if the result display needs more structure
- Expected tests:
- result message separates business validation errors from parsing/technical errors
- invalid workbook and missing required columns remain blocking `UserError`
- row-level errors still let the import continue
## PT-BL-006 - Fix missing default currency on created price index
- Status: open
- Priority: high
- Source bug: [BUG-PT-001](../bugs.md#bug-pt-001---missing-default-currency-in-price-index-automatic-creation)
- Source rule: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
- Requirement: when the market price import automatically creates a missing `price.price`, the created price index must have default currency `USD`.
- Current behavior: the created price index can miss the default currency even though the import specification expects `USD`.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- automatic price index creation assigns `price_currency` to the `USD` currency record
- if the `USD` currency record cannot be found, behavior remains aligned with the rule for optional default references

View File

@@ -1,63 +0,0 @@
# Bugs - Purchase Trade
This file is the bug register for `purchase_trade`.
## Workflow
1. Register every new bug in this file with a stable bug ID.
2. Create a matching backlog entry for every new bug.
3. Link the bug entry to the backlog entry.
4. When fixed, record the implementation commit in the backlog entry.
5. Keep the bug entry as history and update its status.
## Status Values
- `reported`
- `confirmed`
- `backlog-created`
- `in-progress`
- `to-be-tested`
- `done`
- `rejected`
## Bug Template
```md
### BUG-PT-001 - Short title
- Status: `reported`
- Area:
- Reported date: `YYYY-MM-DD`
- Reported by:
- Related rule:
- Backlog entry:
- Severity:
- Environment:
- Description:
- Steps to reproduce:
- Expected behavior:
- Actual behavior:
- Evidence:
- Notes:
```
## Bugs
### BUG-PT-001 - Missing default currency in Price Index automatic creation
- Status: `backlog-created`
- Area: Market Price Import
- Reported date: `2026-05-08`
- Reported by: user
- Related rule: [BR-PT-004 - Market Price Import](business%20rules/market-price-import.md)
- Backlog entry: [PT-BL-006](backlog/market-price-import-backlog.md#pt-bl-006---fix-missing-default-currency-on-created-price-index)
- Severity:
- Environment:
- Description: during market price import, if a price index should be created, then the default currency defined to USD is missing.
- Steps to reproduce:
- import market prices with `Create price index if missing` enabled
- include a `price_index` that does not already exist
- Expected behavior: the automatically created `price.price` has default currency `USD`.
- Actual behavior: the automatically created `price.price` has no default currency.
- Evidence:
- Notes:

View File

@@ -1,50 +0,0 @@
# BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
## Intent
Afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
## Scope
- Domaine: `purchase_trade`
- Flux: templates facture achat et vente
- Priorite: `importante`
## Expected Behavior
Le `FREIGHT VALUE` d'une facture ne doit pas etre pris sur la facture elle-meme.
Il doit etre calcule a partir du `fee.fee` rattache au shipment (`shipment_in`) du lot physique relie a la facture.
Regle de navigation:
- retrouver le lot physique pertinent depuis la facture
- retrouver son shipment
- chercher le `fee.fee` avec:
- `shipment_in = shipment.id`
- `product.name = 'Maritime freight'`
- utiliser `fee.get_amount()` comme montant de fret
La regle s'applique aussi bien aux factures d'achat qu'aux factures de vente.
Cote vente, la remontee doit passer par le lot physique qui fait le lien entre `purchase.line` et `sale.line`.
## Impacted Files
- `modules/purchase_trade/invoice.py`
- `modules/purchase_trade/fee.py`
- `modules/purchase_trade/lot.py`
- Templates facture `.fodt` concernes par `FREIGHT VALUE`
## Tests
Couvrir les changements au plus proche du flux modifie:
- facture achat avec lot physique, shipment et fee `Maritime freight`
- facture vente qui remonte au lot physique puis au shipment
- cas sans fee maritime
- cas sans lot physique pertinent
## Open Questions
- Preciser les templates exacts quand une demande cible un document facture particulier.

View File

@@ -1,59 +0,0 @@
# BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
## Intent
Disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente.
## Scope
- Domaine: `purchase_trade`
- Flux: navigation metier entre achat, vente, shipment et facture
- Priorite: `structurante`
## Expected Behavior
Le lot physique (`lot_type = physic`) porte simultanement le lien vers:
- la `purchase.line` via `lot.line`
- la `sale.line` via `lot.sale_line`
- le shipment via `lot.lot_shipment_in` / `lot.lot_shipment_internal` / `lot.lot_shipment_out`
Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite.
Depuis une facture d'achat:
- remonter a la `purchase.line`
- puis au lot physique de la ligne
- puis au shipment et aux donnees logistiques associees
Depuis une facture de vente:
- remonter a la `sale.line`
- puis au lot physique matchant qui porte aussi la `purchase.line`
- puis au shipment et aux donnees logistiques associees
## Typical Use Cases
- recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location`
- retrouver une facture provisoire liee au lot
- retrouver des fees rattaches au shipment
## Impacted Files
- `modules/purchase_trade/lot.py`
- `modules/purchase_trade/purchase.py`
- `modules/purchase_trade/sale.py`
- `modules/purchase_trade/invoice.py`
- Templates facture relies aux donnees logistiques, si le flux documentaire est concerne.
## Tests
Couvrir les changements au plus proche du flux modifie:
- navigation achat vers lot physique puis shipment
- navigation vente vers lot physique puis shipment
- cas sans lot physique pertinent
## Open Questions
- Documenter au cas par cas les champs exposes au reporting facture quand ils dependent de cette navigation.

View File

@@ -1,106 +0,0 @@
# BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
## Intent
Conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans `lot.qt`.
## Scope
- Domaine: `purchase_trade`
- Flux: modification de `purchase.line.quantity_theorical` apres creation du contrat
- Priorite: `bloquante`
## Inputs
- Une `purchase.line` existe deja.
- Son champ `quantity_theorical` est modifie via `write`.
- Un lot unique de type `virtual` est rattache a la ligne.
## Expected Behavior
Quand `purchase.line.quantity_theorical` est modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur.
La regle s'applique au lot unique de type `virtual` rattache a la `purchase.line`.
Si `delta > 0`:
- augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist`
- augmenter le `lot.qt` ouvert existant
- si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta
Si `delta < 0`:
- diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante
- diminuer la quantite courante du lot `virtual` du meme delta
- si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot`
Definition du `lot.qt` ouvert:
- `lot_p = virtual lot`
- `lot_s = None`
- `lot_shipment_in = None`
- `lot_shipment_internal = None`
- `lot_shipment_out = None`
## Edge Cases
- Si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien.
## Examples
### Exemple E1 - Augmentation simple
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 120`
- `lot.qt ouvert = 40`
- Attendu:
- lot `virtual` augmente de `20`
- `lot.qt ouvert` passe de `40` a `60`
### Exemple E2 - Augmentation sans lot.qt ouvert
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 110`
- aucun `lot.qt` ouvert
- Attendu:
- lot `virtual` augmente de `10`
- creation d'un `lot.qt` ouvert a `10`
### Exemple E3 - Diminution possible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 90`
- `lot.qt ouvert = 25`
- Attendu:
- lot `virtual` diminue de `10`
- `lot.qt ouvert` passe de `25` a `15`
### Exemple E4 - Diminution impossible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 80`
- `lot.qt ouvert = 5`
- Attendu:
- blocage avec `Please unlink or unmatch lot`
## Impacted Files
- `modules/purchase_trade/purchase.py`
- `modules/purchase_trade/lot.py`
## Tests
Couvrir au minimum:
- augmentation avec `lot.qt` ouvert existant
- augmentation sans `lot.qt` ouvert
- diminution possible
- diminution impossible avec erreur
## Source
- Decision metier documentee dans les commentaires de `purchase_trade.purchase.Line.write`.

View File

@@ -1,171 +0,0 @@
# BR-PT-004 - Market Price Import
## Intent
Import dated market prices from an `.xlsx` Excel file into `price.price_value`, using `price.price` as the market price index.
## Scope
- Wizard: `purchase_trade.import_prices`
- Input model: `purchase_trade.import_prices.start`
- Result model: `purchase_trade.import_prices.result`
- Target models:
- `price.price`
- `price.price_value`
- Menu entry: under `price.menu_price`
## Inputs
The wizard reads only the first worksheet of an `.xlsx` file.
The first row is treated as the header row. Header names are normalized by lowercasing and removing non-alphanumeric characters, so labels such as `price_index`, `price index`, and `Price Index` map to the same field.
Required columns:
- `price_index`
- `price_date`
- `high_price`
- `low_price`
- `open_price`
- `price_value`
The wizard options are:
- `Create price index if missing`
- `Overwrite existing price`
## Date and Numeric Parsing
Accepted `price_date` values:
- Excel serial date numbers
- `YYYY-MM-DD`
- `DD/MM/YYYY`
- `MM/DD/YYYY`
Numeric price fields accept decimal commas or decimal points. Empty numeric cells are imported as empty values. Invalid numeric values are reported as row errors.
## Expected Behavior
For each non-empty data row, starting from Excel row 2:
1. Trim and validate `price_index`.
2. Parse `price_date`.
3. Search `price.price` by exact `price_index`.
4. If the price index is missing:
- create it when `Create price index if missing` is checked
- otherwise skip the row with `price_index missing`
5. Search `price.price_value` by `(price, price_date)`.
6. If an existing price value is found:
- update it when `Overwrite existing price` is checked
- otherwise skip the row with `price_date already exists`
7. If no existing price value is found, create a new `price.price_value`.
## Created Price Index Defaults
When the wizard creates a missing `price.price`, it sets:
- `price_index = imported price_index`
- `price_desc = imported price_index`
- `price_curve_type = future`
It also tries to default these references when matching records exist:
- `price_type`: `price.fixtype` where `name = Market price`
- `price_currency`: `currency.currency` where `code = USD`
- `price_calendar`: `price.calendar` where `name = Argus EU`
- `price_unit`: `product.uom` where `name = Mt`
If the `price_index` contains a `YYYY-MM` style period, the wizard derives a `product.month`:
- pattern accepted in the name: `YYYY-MM`, `YYYY/MM`, `YYYY_MM`, `YYYY.MM`, or `YYYY MM`
- month name format: `MONYY`, for example `JUL26`
- if no matching `product.month` exists, it is created with `is_cotation = True`
## Result Reporting
The result screen always shows counts and detail sections for:
- created price indexes
- imported prices
- updated existing prices
- skipped records
- errors
Row-level errors do not stop the whole import; the wizard records the error and continues with the next row.
## Edge Cases
- Missing `price_index`: skipped.
- Missing `price_date`: skipped.
- Invalid `.xlsx` file: blocking `UserError`.
- Missing required columns: blocking `UserError`.
- Invalid date: row error.
- Invalid numeric value: row error.
- Existing `(price, price_date)` without overwrite option: skipped.
- Existing `(price, price_date)` with overwrite option: updated.
- Empty rows are ignored.
## Impacted Files
Direct `purchase_trade` files:
- `modules/purchase_trade/pricing.py`
- `modules/purchase_trade/pricing.xml`
- `modules/purchase_trade/view/import_prices_start_form.xml`
- `modules/purchase_trade/view/import_prices_result_form.xml`
- `modules/purchase_trade/__init__.py`
- `modules/purchase_trade/tryton.cfg`
- `modules/purchase_trade/tests/test_module.py`
External model dependencies:
- `modules/price/price.py`
- `modules/price/price_value.py`
- `modules/price/view/price_value_form.xml`
## Tests
Existing focused tests cover:
- missing price index skipped when creation is disabled
- missing price index created when creation is enabled
- default fields on newly created price indexes
- period reuse/creation from `price_index`
- existing `price_date` skipped when overwrite is disabled
- existing `price_date` updated when overwrite is enabled
- invalid row values collected as errors
- result screen formatting
Recommended additional tests:
- `.xlsx` header normalization
- missing required columns
- Excel serial date parsing
- empty row ignored
- invalid workbook raises `UserError`
## Open Questions
- Q: Should `(price, price_date)` be enforced unique at model/database level?
- A: Enforce uniqueness at model level for now. Do not add a database constraint yet.
- Q: Should created price index defaults remain hardcoded to `Market price`, `USD`, `Argus EU`, and `Mt`?
- A: Yes. Keep these defaults hardcoded for this import.
- Q: Should ambiguous slash dates prefer `DD/MM/YYYY` over `MM/DD/YYYY`, as currently implemented?
- A: Prefer the user's/default locale date format when possible. Fall back to the current order only if no locale preference is available.
- Comment: current code tries `DD/MM/YYYY` before `MM/DD/YYYY` and does not inspect locale. Implementing this answer requires a code change.
- Q: Should missing `price_value` be allowed, or should it skip/error while high/low/open remain optional?
- A: Missing `price_value` is not allowed. Report the row as an error. `high_price`, `low_price`, and `open_price` remain optional.
- Comment: current code allows empty `price_value` and imports it as an empty value. Implementing this answer requires a code change.
- Q: Should duplicate rows for the same `(price_index, price_date)` inside the same Excel file be treated as an error, skipped after the first row, or resolved by the overwrite option?
- A: Report duplicate rows as row errors and do not import or update the duplicate row.
- Q: When `Create price index if missing` is enabled, should missing default reference records (`Market price`, `USD`, `Argus EU`, `Mt`) block index creation or remain optional as currently implemented?
- A: Remain optional. Create the price index with the reference records that can be found.
- Q: Should the import result distinguish business validation errors from technical parsing errors?
- A: Yes. Distinguish business validation errors from file, parsing, and technical errors in the import result.

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