diff --git a/.hgtags b/.hgtags
index ec32b787dd..be137c6c5b 100644
--- a/.hgtags
+++ b/.hgtags
@@ -28,3 +28,1086 @@ df952bd9a9312169cce9a3559a6808fd15ce729f stock_shipment_measurements-5.2.0
5fa8a7ad24a9d9c5726f08a174c22696480bb3c9 stock_shipment_measurements-6.0.0
0000000000000000000000000000000000000000 6.0.1
421aeff2cfde33cdaa789d706a265827407b325b stock_shipment_measurements-6.0.1
+28368573eba198c3524a0ef44c5d1318a71a25b6 2.4.0
+04196701d026d86eb57f91c6e38cddecf257d63f 2.6.0
+099647a527ee5b315f03850b900f656bd10d759f 2.8.0
+06bc610a6f9a26c456050fd61c273bd012a21f04 3.0.0
+e793ef3ca432a0e409aa49bc846bd2c572f3a5ee 3.2.0
+99999433d0e31e2b6f1eb2f589844e835d7b2953 3.4.0
+4c27a1322efba12ae02f1299e7989460ffe8af09 3.6.0
+cca693a2c563e30e5f10fefeca30c49101eb3495 3.8.0
+d961a4a36143eae4ff23dc2c8f7b49c83a8e6b9a 4.0.0
+10eb3100612be23bcac20f5c1036e58aaef76ed7 4.2.0
+d73f07a6109ae14f711d9ac9efe316a4595d3ffa 4.4.0
+893b0270a268be94790a68facfb745cc188d7a93 4.6.0
+eef7e68926b3f9c34ae5dc12bcd0ec6884918c09 4.8.0
+fd97dee9a2f6c5fc4c3ddd7298fdcf4c917ef5d9 5.0.0
+c07759c9b8d3b7733620789a050e7b6de418cf99 5.2.0
+160bbe8565f066ff2e7565fd8a9aceec2c3a0bed 5.4.0
+51f597bea6844c61a7dd658329a0e55e011e5327 5.6.0
+f13147d0020a5a42a7420db6678d2472ccf79bd3 5.8.0
+69d98ca60d7117c6ea722ea3d69fb7896859614b 6.0.0
+0000000000000000000000000000000000000000 2.4.0
+28368573eba198c3524a0ef44c5d1318a71a25b6 stock_split-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+04196701d026d86eb57f91c6e38cddecf257d63f stock_split-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+099647a527ee5b315f03850b900f656bd10d759f stock_split-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+06bc610a6f9a26c456050fd61c273bd012a21f04 stock_split-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+e793ef3ca432a0e409aa49bc846bd2c572f3a5ee stock_split-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+99999433d0e31e2b6f1eb2f589844e835d7b2953 stock_split-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+4c27a1322efba12ae02f1299e7989460ffe8af09 stock_split-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+cca693a2c563e30e5f10fefeca30c49101eb3495 stock_split-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+d961a4a36143eae4ff23dc2c8f7b49c83a8e6b9a stock_split-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+10eb3100612be23bcac20f5c1036e58aaef76ed7 stock_split-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+d73f07a6109ae14f711d9ac9efe316a4595d3ffa stock_split-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+893b0270a268be94790a68facfb745cc188d7a93 stock_split-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+eef7e68926b3f9c34ae5dc12bcd0ec6884918c09 stock_split-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+fd97dee9a2f6c5fc4c3ddd7298fdcf4c917ef5d9 stock_split-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+c07759c9b8d3b7733620789a050e7b6de418cf99 stock_split-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+160bbe8565f066ff2e7565fd8a9aceec2c3a0bed stock_split-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+51f597bea6844c61a7dd658329a0e55e011e5327 stock_split-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+f13147d0020a5a42a7420db6678d2472ccf79bd3 stock_split-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+69d98ca60d7117c6ea722ea3d69fb7896859614b stock_split-6.0.0
+2c1e13c8dd3396c1cd8e638111aac955de3ebc00 1.2.0
+5295eef207dc90e5cd3667c3883675e05726ef57 1.4.0
+2dfd00186d72076cf13379297e19c076884078d9 1.6.0
+056794451985f57a11e3cd7eba52f596ee9ec735 1.8.0
+bd08fecfa2cf0e7e3580c85090f62e1711fcfb88 2.0.0
+fe083045a79de94717fd58ecfa407f9662d05445 2.2.0
+926d98699fe8374bf591a6a21561cb51af46b3cb 2.4.0
+5169c4780f85a719ae92808f229620867a6756ca 2.6.0
+bc954fb77cc0651892cdfe10fb52fda48c238a98 2.8.0
+bb15a9f03156fea571be3216d502e5d4dc92439c 3.0.0
+7887dba041046609d589e9db4d3d6cf154d84865 3.2.0
+13c061d1e265b64a17a67ab05b2419ea17a10e7b 3.4.0
+8cd9280b5c4df641c5c969f020cec2d7d291082e 3.6.0
+3cd313fcfb1603d6ba340e254fc09ac6cfd1c420 3.8.0
+f36bf15e0049da995257277971560f979e534808 4.0.0
+a0f86fdcff3f7146eba32e4c06107cbadf7e52e5 4.2.0
+33bc557f7dde9e3ed40d6cc8e11b98eda63239a5 4.4.0
+32bafb5576e02e637109040302e14ff20f32e3a4 4.6.0
+1567de11c58a7052bba71f735826f332ece9ef35 4.8.0
+30611743f9cfe3c23bf8d252b971b011ea54ad53 5.0.0
+39be3138aa4546c555cf9ca5d800bb4ec3c3d7cb 5.2.0
+91f140df04060667e5faab3bf8d1095cab3f5528 5.4.0
+dbec1eac9dc4167e1307adbc62ef72bc7937d5ef 5.6.0
+00c0bad66396daf07d8c5f78dd8dae4ed0e421a4 5.8.0
+5a0e8f4810783920576928b183e3733693959da8 6.0.0
+0000000000000000000000000000000000000000 1.2.0
+2c1e13c8dd3396c1cd8e638111aac955de3ebc00 stock_supply_day-1.2.0
+0000000000000000000000000000000000000000 1.4.0
+5295eef207dc90e5cd3667c3883675e05726ef57 stock_supply_day-1.4.0
+0000000000000000000000000000000000000000 1.6.0
+2dfd00186d72076cf13379297e19c076884078d9 stock_supply_day-1.6.0
+0000000000000000000000000000000000000000 1.8.0
+056794451985f57a11e3cd7eba52f596ee9ec735 stock_supply_day-1.8.0
+0000000000000000000000000000000000000000 2.0.0
+bd08fecfa2cf0e7e3580c85090f62e1711fcfb88 stock_supply_day-2.0.0
+0000000000000000000000000000000000000000 2.2.0
+fe083045a79de94717fd58ecfa407f9662d05445 stock_supply_day-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+926d98699fe8374bf591a6a21561cb51af46b3cb stock_supply_day-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+5169c4780f85a719ae92808f229620867a6756ca stock_supply_day-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+bc954fb77cc0651892cdfe10fb52fda48c238a98 stock_supply_day-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+bb15a9f03156fea571be3216d502e5d4dc92439c stock_supply_day-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+7887dba041046609d589e9db4d3d6cf154d84865 stock_supply_day-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+13c061d1e265b64a17a67ab05b2419ea17a10e7b stock_supply_day-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+8cd9280b5c4df641c5c969f020cec2d7d291082e stock_supply_day-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+3cd313fcfb1603d6ba340e254fc09ac6cfd1c420 stock_supply_day-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+f36bf15e0049da995257277971560f979e534808 stock_supply_day-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+a0f86fdcff3f7146eba32e4c06107cbadf7e52e5 stock_supply_day-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+33bc557f7dde9e3ed40d6cc8e11b98eda63239a5 stock_supply_day-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+32bafb5576e02e637109040302e14ff20f32e3a4 stock_supply_day-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+1567de11c58a7052bba71f735826f332ece9ef35 stock_supply_day-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+30611743f9cfe3c23bf8d252b971b011ea54ad53 stock_supply_day-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+39be3138aa4546c555cf9ca5d800bb4ec3c3d7cb stock_supply_day-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+91f140df04060667e5faab3bf8d1095cab3f5528 stock_supply_day-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+dbec1eac9dc4167e1307adbc62ef72bc7937d5ef stock_supply_day-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+00c0bad66396daf07d8c5f78dd8dae4ed0e421a4 stock_supply_day-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+5a0e8f4810783920576928b183e3733693959da8 stock_supply_day-6.0.0
+0834d76da22334e9c2b1e9d89ba50ac38bd3907f 2.2.0
+3c285f5f45031efbf9966541ace27b80fa45129d 2.4.0
+850476a6cbe95cf9bead5fdfaf094231908623ae 2.6.0
+3e0634468842969d6b2ee5e0f0c69a5aa75088e0 2.8.0
+3a26b77c883afebe780e59cd591c85f86248479c 3.0.0
+e6944d53c9579ff7d529f2fcadc021dc5a96d76c 3.2.0
+749efb5e0a4e86fb5ce3d5f181e73bc61ccd40e8 3.4.0
+370823db908f443f81bae475974c57af6281236b 3.6.0
+ba552f7b9b5297a6cde2372eebc523945d535c93 3.8.0
+0c88f450cac4029d9aaa81e94bc24457aec7c54a 4.0.0
+f0df25bfdb09fc1875f336bb870aa140a3247bcd 4.2.0
+afc2f1896f1c22b4591147b48f03743886e390f2 4.4.0
+5d6cdbfbb2b37451d4875ff9e789bf1350e75ab7 4.6.0
+df25bbcc27ede6b2a09868c7239732c3cd5e2c2a 4.8.0
+df42ccdd98a6fb0b4f55cc24e94f077fec1ffeb2 5.0.0
+d8a1fdd9db7f020437f2d5f02311ab29fe0cbab4 5.2.0
+c4c090246916fe2e00d67a26cb9859ea0a7106b5 5.4.0
+b89599b1cef0267371ce55e1b41cb1958d3312ba 5.6.0
+757b5e5484dd448afcbec95e87c21ecfad938b5f 5.8.0
+579edbea3863be427cfc1bb76cec03d061732d90 6.0.0
+0000000000000000000000000000000000000000 2.2.0
+0834d76da22334e9c2b1e9d89ba50ac38bd3907f stock_supply_forecast-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+3c285f5f45031efbf9966541ace27b80fa45129d stock_supply_forecast-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+850476a6cbe95cf9bead5fdfaf094231908623ae stock_supply_forecast-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+3e0634468842969d6b2ee5e0f0c69a5aa75088e0 stock_supply_forecast-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+3a26b77c883afebe780e59cd591c85f86248479c stock_supply_forecast-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+e6944d53c9579ff7d529f2fcadc021dc5a96d76c stock_supply_forecast-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+749efb5e0a4e86fb5ce3d5f181e73bc61ccd40e8 stock_supply_forecast-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+370823db908f443f81bae475974c57af6281236b stock_supply_forecast-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+ba552f7b9b5297a6cde2372eebc523945d535c93 stock_supply_forecast-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+0c88f450cac4029d9aaa81e94bc24457aec7c54a stock_supply_forecast-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+f0df25bfdb09fc1875f336bb870aa140a3247bcd stock_supply_forecast-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+afc2f1896f1c22b4591147b48f03743886e390f2 stock_supply_forecast-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+5d6cdbfbb2b37451d4875ff9e789bf1350e75ab7 stock_supply_forecast-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+df25bbcc27ede6b2a09868c7239732c3cd5e2c2a stock_supply_forecast-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+df42ccdd98a6fb0b4f55cc24e94f077fec1ffeb2 stock_supply_forecast-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+d8a1fdd9db7f020437f2d5f02311ab29fe0cbab4 stock_supply_forecast-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+c4c090246916fe2e00d67a26cb9859ea0a7106b5 stock_supply_forecast-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+b89599b1cef0267371ce55e1b41cb1958d3312ba stock_supply_forecast-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+757b5e5484dd448afcbec95e87c21ecfad938b5f stock_supply_forecast-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+579edbea3863be427cfc1bb76cec03d061732d90 stock_supply_forecast-6.0.0
+bb2bb6af62f06218b0c8c689b24dd16f43307ca1 1.0.0
+5b0a8261963f167230f23a1b97c8b38519428143 1.2.0
+396ad2807deb26c6c353eb119dc50fd4fdcbb28a 1.4.0
+b44aa993ce24453fdb2362187e201a9375bcaa09 1.6.0
+f76056df60f581f803c7a7e4cec81397e4bbbb9c 1.8.0
+354dfc672c4b3ee2609a4051f6d8d431dd087231 2.0.0
+0881ad5cb82f3f00ec1eea276fa100a957691982 2.2.0
+5eb0f23e5774dddbf43cafe34bf527e1cd78d619 2.4.0
+6cd5f943826cb858bc34d20074bfd81b715114f8 2.6.0
+69f57817f6f0ed6ee0788cfab959a9321fcb45bb 2.8.0
+2ebe2636b804a2f7635d0af5b1030ced08de7a37 3.0.0
+c466a41ecc7586e898f0f4f2341c2e74b41ea816 3.2.0
+ddd6516306dd09e3049fb95ff94b4ce48687e200 3.4.0
+e122da9f78841562722820efbf4d64e4c390e8af 3.6.0
+5dcc2dd84f2145e78882af143cad5111c31f09b6 3.8.0
+c0a58dc8306d6885126b9613ad4b36e0a95b75f0 4.0.0
+afc53d87c88438c9c8e56629e994836844e32dbe 4.2.0
+7bbc6abdafa0642baf4095654151d70c9e5482c4 4.4.0
+99eaaa78814833e4d21e9d96e8c110f061f0edfa 4.6.0
+3b5c655f3c26831a505bd74493f108425d42bc62 4.8.0
+1c163e9beae826b96a72cd0d2c59bf4899241f82 5.0.0
+c139a716d188bf7fbf46574fa84a473133b8bee2 5.2.0
+9264fc010a2d2acb3128d9208977bd6b77fb5160 5.4.0
+2d2feece662966a006078304aa61b695447cef2f 5.6.0
+5756606b07d016272254398e15ae07873a72c230 5.8.0
+d527c60bfe0bc723779200d118179faa7f96ba4a 6.0.0
+7c13addfa07217e498c00f83117517b179d33f1b 6.0.1
+47e2a56c3eb9cd49ad460d35fc16dab4c334384b 6.0.2
+aeafed90f31c18b914b74cfd675f897dc41ca6cd 6.0.3
+0000000000000000000000000000000000000000 1.0.0
+bb2bb6af62f06218b0c8c689b24dd16f43307ca1 stock_supply-1.0.0
+0000000000000000000000000000000000000000 1.2.0
+5b0a8261963f167230f23a1b97c8b38519428143 stock_supply-1.2.0
+0000000000000000000000000000000000000000 1.4.0
+396ad2807deb26c6c353eb119dc50fd4fdcbb28a stock_supply-1.4.0
+0000000000000000000000000000000000000000 1.6.0
+b44aa993ce24453fdb2362187e201a9375bcaa09 stock_supply-1.6.0
+0000000000000000000000000000000000000000 1.8.0
+f76056df60f581f803c7a7e4cec81397e4bbbb9c stock_supply-1.8.0
+0000000000000000000000000000000000000000 2.0.0
+354dfc672c4b3ee2609a4051f6d8d431dd087231 stock_supply-2.0.0
+0000000000000000000000000000000000000000 2.2.0
+0881ad5cb82f3f00ec1eea276fa100a957691982 stock_supply-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+5eb0f23e5774dddbf43cafe34bf527e1cd78d619 stock_supply-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+6cd5f943826cb858bc34d20074bfd81b715114f8 stock_supply-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+69f57817f6f0ed6ee0788cfab959a9321fcb45bb stock_supply-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+2ebe2636b804a2f7635d0af5b1030ced08de7a37 stock_supply-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+c466a41ecc7586e898f0f4f2341c2e74b41ea816 stock_supply-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+ddd6516306dd09e3049fb95ff94b4ce48687e200 stock_supply-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+e122da9f78841562722820efbf4d64e4c390e8af stock_supply-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+5dcc2dd84f2145e78882af143cad5111c31f09b6 stock_supply-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+c0a58dc8306d6885126b9613ad4b36e0a95b75f0 stock_supply-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+afc53d87c88438c9c8e56629e994836844e32dbe stock_supply-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+7bbc6abdafa0642baf4095654151d70c9e5482c4 stock_supply-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+99eaaa78814833e4d21e9d96e8c110f061f0edfa stock_supply-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+3b5c655f3c26831a505bd74493f108425d42bc62 stock_supply-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+1c163e9beae826b96a72cd0d2c59bf4899241f82 stock_supply-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+c139a716d188bf7fbf46574fa84a473133b8bee2 stock_supply-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+9264fc010a2d2acb3128d9208977bd6b77fb5160 stock_supply-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+2d2feece662966a006078304aa61b695447cef2f stock_supply-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+5756606b07d016272254398e15ae07873a72c230 stock_supply-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+d527c60bfe0bc723779200d118179faa7f96ba4a stock_supply-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+7c13addfa07217e498c00f83117517b179d33f1b stock_supply-6.0.1
+0000000000000000000000000000000000000000 6.0.2
+47e2a56c3eb9cd49ad460d35fc16dab4c334384b stock_supply-6.0.2
+0000000000000000000000000000000000000000 6.0.3
+aeafed90f31c18b914b74cfd675f897dc41ca6cd stock_supply-6.0.3
+ec2752f736f671fea3f95f881fa19e7cbfcc88c2 2.4.0
+4541105a8fc9586b5c65f581d70dd6906c41a730 2.6.0
+c0a9cb20a609d38ecf4facd08ffe55b2020e38ad 2.8.0
+ddf4cfa5af01e368d995885606d572d53d36cd7b 3.0.0
+81e986b99d1b16b26e8c7ae69684225bfa252603 3.2.0
+808383c0929c8dda2fab252418dbf31b46c2dffc 3.4.0
+bf11ecaa1d599160a62a02f46639955aaaa472a3 3.6.0
+07c3ef01edffc285a49cb969e18737dfac67bfdc 3.8.0
+d41ca7fbc924d4f73aff1c8377d2f700abe06f2d 4.0.0
+b58e60f41b662ac3d58c7502c4a6b4d5018797f4 4.2.0
+52078228f2c56880ac08f6987e8383c08a8043fd 4.4.0
+5df3455491d72296ada960cb45f72a276433a45d 4.6.0
+769117cd528a585fda8ea67bad5b54bf6fe5f6a6 4.8.0
+25c05b49d46071031d53a7798a711246789d6973 5.0.0
+24c941bdf02946e4bc130135508158dcb9817c39 5.2.0
+d4b5b3fa95da83bb9439320e69f4df43b9360f9c 5.4.0
+0b460de8ca80a58dc7b4d4528c6c4587fa0d16eb 5.6.0
+f0209c41aa97aaf6a1c8da050e47f24ad289aa38 5.8.0
+6f70ee75254fa62addab73552b06b39a2e151e66 6.0.0
+4b8288280406ba3d9a4bfb3c96a84a29ddcb9e23 6.0.1
+0000000000000000000000000000000000000000 2.4.0
+ec2752f736f671fea3f95f881fa19e7cbfcc88c2 stock_supply_production-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+4541105a8fc9586b5c65f581d70dd6906c41a730 stock_supply_production-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+c0a9cb20a609d38ecf4facd08ffe55b2020e38ad stock_supply_production-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+ddf4cfa5af01e368d995885606d572d53d36cd7b stock_supply_production-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+81e986b99d1b16b26e8c7ae69684225bfa252603 stock_supply_production-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+808383c0929c8dda2fab252418dbf31b46c2dffc stock_supply_production-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+bf11ecaa1d599160a62a02f46639955aaaa472a3 stock_supply_production-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+07c3ef01edffc285a49cb969e18737dfac67bfdc stock_supply_production-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+d41ca7fbc924d4f73aff1c8377d2f700abe06f2d stock_supply_production-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+b58e60f41b662ac3d58c7502c4a6b4d5018797f4 stock_supply_production-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+52078228f2c56880ac08f6987e8383c08a8043fd stock_supply_production-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+5df3455491d72296ada960cb45f72a276433a45d stock_supply_production-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+769117cd528a585fda8ea67bad5b54bf6fe5f6a6 stock_supply_production-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+25c05b49d46071031d53a7798a711246789d6973 stock_supply_production-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+24c941bdf02946e4bc130135508158dcb9817c39 stock_supply_production-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+d4b5b3fa95da83bb9439320e69f4df43b9360f9c stock_supply_production-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+0b460de8ca80a58dc7b4d4528c6c4587fa0d16eb stock_supply_production-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+f0209c41aa97aaf6a1c8da050e47f24ad289aa38 stock_supply_production-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+6f70ee75254fa62addab73552b06b39a2e151e66 stock_supply_production-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+4b8288280406ba3d9a4bfb3c96a84a29ddcb9e23 stock_supply_production-6.0.1
+b9082c1aa7e6cc8316754bca8f2b29c7018ef9fc 3.0.0
+1dbac5dcf15dc3f17fb4865387a9e171dd20fc70 3.2.0
+db5b4bb6b6b098dd55089c2923e3eb5e1b8ec56b 3.4.0
+b7cb20ab4ec3b50821983ed8c0b034f30752a6ec 3.6.0
+7961a5a68240535525babbcdf496952107a480bf 3.8.0
+615333cf31aa9cc78cba9d7e20c515752e448fdb 4.0.0
+eaf2542a8adbf470a3249429f44cd5c70fa6a986 4.2.0
+6bd47a06f582784dfca255c49c7877d614adec51 4.4.0
+486b4928c849a93beb92d3bdfecfc8f622a38956 4.6.0
+03ded560c597cabf1a3e7df1fb586ecde33c1529 4.8.0
+7fcc5447286ed6be08252a52014a229fb21e706b 5.0.0
+25dfcaa29bcd9d57cdf019b3cae55b6226c355f6 5.2.0
+d4b86ad1ba39b874c39da9a5a007690489f4e15f 5.4.0
+f55c3c6bdd16d4b2ce85c8d691e9cafa71ce3d1f 5.6.0
+74c459fcf1a4a1237e25c4e3a63adbed65a7d1a8 5.8.0
+71a5bc1d16dc5b2b613ab21d94116f74d3b99320 6.0.0
+0000000000000000000000000000000000000000 3.0.0
+b9082c1aa7e6cc8316754bca8f2b29c7018ef9fc timesheet_cost-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+1dbac5dcf15dc3f17fb4865387a9e171dd20fc70 timesheet_cost-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+db5b4bb6b6b098dd55089c2923e3eb5e1b8ec56b timesheet_cost-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+b7cb20ab4ec3b50821983ed8c0b034f30752a6ec timesheet_cost-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+7961a5a68240535525babbcdf496952107a480bf timesheet_cost-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+615333cf31aa9cc78cba9d7e20c515752e448fdb timesheet_cost-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+eaf2542a8adbf470a3249429f44cd5c70fa6a986 timesheet_cost-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+6bd47a06f582784dfca255c49c7877d614adec51 timesheet_cost-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+486b4928c849a93beb92d3bdfecfc8f622a38956 timesheet_cost-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+03ded560c597cabf1a3e7df1fb586ecde33c1529 timesheet_cost-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+7fcc5447286ed6be08252a52014a229fb21e706b timesheet_cost-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+25dfcaa29bcd9d57cdf019b3cae55b6226c355f6 timesheet_cost-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+d4b86ad1ba39b874c39da9a5a007690489f4e15f timesheet_cost-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+f55c3c6bdd16d4b2ce85c8d691e9cafa71ce3d1f timesheet_cost-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+74c459fcf1a4a1237e25c4e3a63adbed65a7d1a8 timesheet_cost-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+71a5bc1d16dc5b2b613ab21d94116f74d3b99320 timesheet_cost-6.0.0
+a32068d131a6872da6c87812f3fda96c5ad076bb 1.4.0
+3d405f7a2a43e41869b7f438203d3a945b1d68f9 1.6.0
+65577163f1e51a0e1385155cae93eb2900866f3b 1.8.0
+24d03108a830bc2b71924432ebcb32dfd6f5b2e0 2.0.0
+1d020661487e0ddc14d2825aa66ec6772e66010c 2.2.0
+7b90dcce007c3fc93a9b409f54f379f8182728ee 2.4.0
+549128cae2441f891c24176c1f6a67777962e3d3 2.6.0
+8c69d08939dbc773d89f4c637ce6a8d8cadcb866 2.8.0
+4e635a95118453b2ff0ec533a70ac50d4dacc3cb 3.0.0
+5f4ed300d4966e0c16c4497428a130b19a6209f3 3.2.0
+a4e84c96f4735bd710e02a0364af0947ba44d4b7 3.4.0
+728c1ee800a32fdd4856e86998a32c3c929d262d 3.6.0
+1924468dda905272353c147edc625a3496b3421e 3.8.0
+ae80ff03e20174f8ed595b9fa47893598c13f91f 4.0.0
+dd6dfdad58d95b23dab3c1ff906f2e18c634b474 4.2.0
+05cf4e50160ca0801ef2aa7c93d2b9b9c4a41071 4.4.0
+4ad8501eef3e1b47ccf54ecabf5cedbb0bebe99b 4.6.0
+bd8398d102d53692be26bd76171c23b85676304c 4.8.0
+f22152a613c52f8ffece522ec3a1f30e46637e3a 5.0.0
+ae0426e8137939d8d4e14f0d6717b46e0bd6ff3a 5.2.0
+41c1f6178f3f039bc62dd85ebd1fa6f92212c9ec 5.4.0
+f9ad969ee981b3b7cd11723d471d4ed3d0fff873 5.6.0
+a874b70acc06493a4b64edc59ec3be3cba8503a6 5.8.0
+779126589e978c55a09cc15d7854a08fff370416 6.0.0
+8603d8d89e44cb5ad8a8c9efc403db90cd214028 6.0.1
+d2f9bdfd51c32bc1c67d14357acab16782dedb07 6.0.2
+0000000000000000000000000000000000000000 1.4.0
+a32068d131a6872da6c87812f3fda96c5ad076bb timesheet-1.4.0
+0000000000000000000000000000000000000000 1.6.0
+3d405f7a2a43e41869b7f438203d3a945b1d68f9 timesheet-1.6.0
+0000000000000000000000000000000000000000 1.8.0
+65577163f1e51a0e1385155cae93eb2900866f3b timesheet-1.8.0
+0000000000000000000000000000000000000000 2.0.0
+24d03108a830bc2b71924432ebcb32dfd6f5b2e0 timesheet-2.0.0
+0000000000000000000000000000000000000000 2.2.0
+1d020661487e0ddc14d2825aa66ec6772e66010c timesheet-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+7b90dcce007c3fc93a9b409f54f379f8182728ee timesheet-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+549128cae2441f891c24176c1f6a67777962e3d3 timesheet-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+8c69d08939dbc773d89f4c637ce6a8d8cadcb866 timesheet-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+4e635a95118453b2ff0ec533a70ac50d4dacc3cb timesheet-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+5f4ed300d4966e0c16c4497428a130b19a6209f3 timesheet-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+a4e84c96f4735bd710e02a0364af0947ba44d4b7 timesheet-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+728c1ee800a32fdd4856e86998a32c3c929d262d timesheet-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+1924468dda905272353c147edc625a3496b3421e timesheet-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+ae80ff03e20174f8ed595b9fa47893598c13f91f timesheet-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+dd6dfdad58d95b23dab3c1ff906f2e18c634b474 timesheet-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+05cf4e50160ca0801ef2aa7c93d2b9b9c4a41071 timesheet-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+4ad8501eef3e1b47ccf54ecabf5cedbb0bebe99b timesheet-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+bd8398d102d53692be26bd76171c23b85676304c timesheet-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+f22152a613c52f8ffece522ec3a1f30e46637e3a timesheet-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+ae0426e8137939d8d4e14f0d6717b46e0bd6ff3a timesheet-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+41c1f6178f3f039bc62dd85ebd1fa6f92212c9ec timesheet-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+f9ad969ee981b3b7cd11723d471d4ed3d0fff873 timesheet-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+a874b70acc06493a4b64edc59ec3be3cba8503a6 timesheet-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+779126589e978c55a09cc15d7854a08fff370416 timesheet-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+8603d8d89e44cb5ad8a8c9efc403db90cd214028 timesheet-6.0.1
+0000000000000000000000000000000000000000 6.0.2
+d2f9bdfd51c32bc1c67d14357acab16782dedb07 timesheet-6.0.2
+0f3d5527aad7e83e07c84b19983b86f5b580d9b9 5.2.0
+32c3f6707c841e89eaa8e1200b4ed4b4b8c2510d 5.4.0
+65a1ddcc0b7d681c785f9260aa11e9c9a9b3f74b 5.6.0
+8766a61dd6f885faa2bed9eaa21c307412674989 5.8.0
+5c50392d0b77f4f5c1f76ece0997ec73591e887c 6.0.0
+0000000000000000000000000000000000000000 5.2.0
+0f3d5527aad7e83e07c84b19983b86f5b580d9b9 user_role-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+32c3f6707c841e89eaa8e1200b4ed4b4b8c2510d user_role-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+65a1ddcc0b7d681c785f9260aa11e9c9a9b3f74b user_role-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+8766a61dd6f885faa2bed9eaa21c307412674989 user_role-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+5c50392d0b77f4f5c1f76ece0997ec73591e887c user_role-6.0.0
+cc6273db844d391455840a0cb63fbd8e78b704c7 5.8.0
+4020d5ca9497c14dfc273676d49363650eb8da80 6.0.0
+0050d9a729746f753fbd3029ff635a459e458e07 6.0.1
+64b26bac37642e1dcff5f8d9590250cbff8fbc94 6.0.2
+247fec368df174a2cc186065669462b198b3fd62 6.0.3
+0000000000000000000000000000000000000000 5.8.0
+cc6273db844d391455840a0cb63fbd8e78b704c7 web_shop-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+4020d5ca9497c14dfc273676d49363650eb8da80 web_shop-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+0050d9a729746f753fbd3029ff635a459e458e07 web_shop-6.0.1
+0000000000000000000000000000000000000000 6.0.2
+64b26bac37642e1dcff5f8d9590250cbff8fbc94 web_shop-6.0.2
+0000000000000000000000000000000000000000 6.0.3
+247fec368df174a2cc186065669462b198b3fd62 web_shop-6.0.3
+bfc8e45097fc0e414744408e96708fdf5d2b33a1 5.8.0
+2efa183d80e356d0c11731819456b8dd9bc586db 6.0.0
+1f0c1ae839cf3df054309a0aa60e6e7fcdbdc926 6.0.1
+c1149124f1c8206c8b3d787dc9d7f41e61c0fe20 6.0.2
+255623f33107cd9f26a00f89c727634bb01cf1a9 6.0.3
+33f7271faaeccb49772f3ce8dc6e35036d7aec61 6.0.4
+2af6bbee8a75aaca6a213f722f1e4b49d98c551e 6.0.5
+0000000000000000000000000000000000000000 5.8.0
+bfc8e45097fc0e414744408e96708fdf5d2b33a1 web_shop_vue_storefront-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+2efa183d80e356d0c11731819456b8dd9bc586db web_shop_vue_storefront-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+1f0c1ae839cf3df054309a0aa60e6e7fcdbdc926 web_shop_vue_storefront-6.0.1
+0000000000000000000000000000000000000000 6.0.2
+c1149124f1c8206c8b3d787dc9d7f41e61c0fe20 web_shop_vue_storefront-6.0.2
+0000000000000000000000000000000000000000 6.0.3
+255623f33107cd9f26a00f89c727634bb01cf1a9 web_shop_vue_storefront-6.0.3
+0000000000000000000000000000000000000000 6.0.4
+33f7271faaeccb49772f3ce8dc6e35036d7aec61 web_shop_vue_storefront-6.0.4
+0000000000000000000000000000000000000000 6.0.5
+2af6bbee8a75aaca6a213f722f1e4b49d98c551e web_shop_vue_storefront-6.0.5
+0a5ab9e8e526e89b69c1aadb73127da0759edc27 5.8.0
+4b01e563d74ede2cd6ac3c0ae8bd597a963c21a3 6.0.0
+c418c973cfc6b60e495a3376cd632035b9a1a7d8 6.0.1
+0000000000000000000000000000000000000000 5.8.0
+0a5ab9e8e526e89b69c1aadb73127da0759edc27 web_shop_vue_storefront_stripe-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+4b01e563d74ede2cd6ac3c0ae8bd597a963c21a3 web_shop_vue_storefront_stripe-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+c418c973cfc6b60e495a3376cd632035b9a1a7d8 web_shop_vue_storefront_stripe-6.0.1
+d1340478469878c66528aac5cec001841d14ca5a 5.2.0
+bed84c6dd812463897dc9cca5b7909f6ea4651a4 5.4.0
+e37a9b2291b9b60d453a9eabc894d21b5aeca097 5.6.0
+c3046a83c61339a6310b8cf56c2e4dddd59a0675 5.8.0
+0d2662846f667e44e2831a271becf86f8868c9a5 6.0.0
+f5cdb780a081fb87e0ebcb77b71e5b1c9f7233df 6.0.1
+0000000000000000000000000000000000000000 5.2.0
+d1340478469878c66528aac5cec001841d14ca5a web_shortener-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+bed84c6dd812463897dc9cca5b7909f6ea4651a4 web_shortener-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+e37a9b2291b9b60d453a9eabc894d21b5aeca097 web_shortener-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+c3046a83c61339a6310b8cf56c2e4dddd59a0675 web_shortener-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+0d2662846f667e44e2831a271becf86f8868c9a5 web_shortener-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+f5cdb780a081fb87e0ebcb77b71e5b1c9f7233df web_shortener-6.0.1
+99f99784b341ce8bb34bd70d89f51aa45c24f1f7 4.2.0
+a6661fddc44d1cee9c5697d983e8fb38deb73225 4.4.0
+d5c1f780f492de8ab82da98a577a5685bb513d9b 4.6.0
+9ad3bd829ad68cd69806533416aadc50f5eb6794 4.8.0
+ca21f3a99b42b088c6588c36f5e105dae713f775 5.0.0
+c2ef0488218d9d3ee59514b744e2145bbd0147b3 5.2.0
+cb5da130470815adfd7b3681fa2803feed934f45 5.4.0
+b95009d0a58dd058440cc0cd7537e5c4499ea16a 5.6.0
+166c1e4c476baeb36b21cef7963b87b5ca07f71e 5.8.0
+ec9a4f3e26f796c12a267e4b3af717fd2a35c066 6.0.0
+0000000000000000000000000000000000000000 4.2.0
+99f99784b341ce8bb34bd70d89f51aa45c24f1f7 web_user-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+a6661fddc44d1cee9c5697d983e8fb38deb73225 web_user-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+d5c1f780f492de8ab82da98a577a5685bb513d9b web_user-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+9ad3bd829ad68cd69806533416aadc50f5eb6794 web_user-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+ca21f3a99b42b088c6588c36f5e105dae713f775 web_user-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+c2ef0488218d9d3ee59514b744e2145bbd0147b3 web_user-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+cb5da130470815adfd7b3681fa2803feed934f45 web_user-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+b95009d0a58dd058440cc0cd7537e5c4499ea16a web_user-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+166c1e4c476baeb36b21cef7963b87b5ca07f71e web_user-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+ec9a4f3e26f796c12a267e4b3af717fd2a35c066 web_user-6.0.0
+3974f546b848d4ac59551db9f733d8ed62946dfd 1.8.0
+c8fdae3b2338b744fce709572958884a95de87b3 2.0.0
+120d0435125cf3a3c28280321962571924ad422f 2.2.0
+ef9846cb18afe62182de446d8e4fa7b8393f7168 2.4.0
+648c43939fd698988bde5b54f7a1dff9d47922af 2.6.0
+5625ce03643f9f2b69659907754e5b0e924c689f 2.8.0
+31740f7e08a0c449ce6715e1ef15392fb6326ce9 3.0.0
+40affcdda4629de37997d0e5c7d3fe9ca2806b12 3.2.0
+61013c4482fc95a61d2a15a8b21574a706755b10 3.4.0
+2a2503ebd25887ff3668d470809c00c5bcf2c6c7 3.6.0
+1e741a18e2a1f29fcc66e9ab4878554a4e63a355 3.8.0
+403f21dd1bb7059a0928c46c8b21a97c14f9c720 4.0.0
+d5b1e8f047748526d9a2cd354da0e86d617ec4c7 4.2.0
+365af7e74c2baae38e96bc0afb1deae2d2b44ac0 4.4.0
+cd4731fb373698d480a625a286de776a0b9bdc6e 4.6.0
+3867b51d50056fda52882150a017a9dce6811264 4.8.0
+e86f9d540f29dfcd39e8f198b50940c8b91250d6 5.0.0
+f4a0b0e282f39c013c44e5012e6b85e4b8a4ae40 5.2.0
+03daaf3994610fde107d98d8458b2731e03c5306 5.4.0
+a8061a1d268776766c44ceb97094d229dbe31962 5.6.0
+8bc88e82ff9edc33725d08bc0c6f2862f221a127 5.8.0
+ac15e7f1881fd78d30eac0d7c772638265830c77 6.0.0
+14cd94a3e75463a9e4ab4c562cce82470070731b 6.0.1
+b579da9fa80282eaf0657e6d2826147c2c6c2f3d 6.0.2
+dc3d781b552d24dcfb7b0ced4322b1ba17e6ca09 6.0.3
+8e6d0969604981e6d024b203424b1b3f0bcd4cb3 6.0.4
+c20749d242200840432ab0b87ce7c0072ee1be34 6.0.5
+b279acacc05c6df60ca9833a3eb6e22b9f9fa488 6.0.6
+8a5195f7cece07c1baceade758e1e783170abe32 6.0.7
+0000000000000000000000000000000000000000 1.8.0
+3974f546b848d4ac59551db9f733d8ed62946dfd proteus-1.8.0
+0000000000000000000000000000000000000000 2.0.0
+c8fdae3b2338b744fce709572958884a95de87b3 proteus-2.0.0
+0000000000000000000000000000000000000000 2.2.0
+120d0435125cf3a3c28280321962571924ad422f proteus-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+ef9846cb18afe62182de446d8e4fa7b8393f7168 proteus-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+648c43939fd698988bde5b54f7a1dff9d47922af proteus-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+5625ce03643f9f2b69659907754e5b0e924c689f proteus-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+31740f7e08a0c449ce6715e1ef15392fb6326ce9 proteus-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+40affcdda4629de37997d0e5c7d3fe9ca2806b12 proteus-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+61013c4482fc95a61d2a15a8b21574a706755b10 proteus-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+2a2503ebd25887ff3668d470809c00c5bcf2c6c7 proteus-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+1e741a18e2a1f29fcc66e9ab4878554a4e63a355 proteus-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+403f21dd1bb7059a0928c46c8b21a97c14f9c720 proteus-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+d5b1e8f047748526d9a2cd354da0e86d617ec4c7 proteus-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+365af7e74c2baae38e96bc0afb1deae2d2b44ac0 proteus-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+cd4731fb373698d480a625a286de776a0b9bdc6e proteus-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+3867b51d50056fda52882150a017a9dce6811264 proteus-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+e86f9d540f29dfcd39e8f198b50940c8b91250d6 proteus-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+f4a0b0e282f39c013c44e5012e6b85e4b8a4ae40 proteus-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+03daaf3994610fde107d98d8458b2731e03c5306 proteus-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+a8061a1d268776766c44ceb97094d229dbe31962 proteus-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+8bc88e82ff9edc33725d08bc0c6f2862f221a127 proteus-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+ac15e7f1881fd78d30eac0d7c772638265830c77 proteus-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+14cd94a3e75463a9e4ab4c562cce82470070731b proteus-6.0.1
+0000000000000000000000000000000000000000 6.0.2
+b579da9fa80282eaf0657e6d2826147c2c6c2f3d proteus-6.0.2
+0000000000000000000000000000000000000000 6.0.3
+dc3d781b552d24dcfb7b0ced4322b1ba17e6ca09 proteus-6.0.3
+0000000000000000000000000000000000000000 6.0.4
+8e6d0969604981e6d024b203424b1b3f0bcd4cb3 proteus-6.0.4
+0000000000000000000000000000000000000000 6.0.5
+c20749d242200840432ab0b87ce7c0072ee1be34 proteus-6.0.5
+0000000000000000000000000000000000000000 6.0.6
+b279acacc05c6df60ca9833a3eb6e22b9f9fa488 proteus-6.0.6
+0000000000000000000000000000000000000000 6.0.7
+8a5195f7cece07c1baceade758e1e783170abe32 proteus-6.0.7
+c835144ba5fd172c9aa8714683e69ca60bc894bc 3.8.0
+20712dc81634af43f9e0ebb41b0dcea50ccffdd2 4.0.0
+963504a30207af5877c11368ac5dadbc5d28fee1 4.2.0
+1fea2b717dd8d57d0a233c19d7f1eec35bdde0fa 4.4.0
+16a536201007ba66d36040a34e79a8998c4051f8 4.6.0
+08b000057b0b5a69f4034f2e1f5c7f2f7f23276f 4.8.0
+47edb75271514576ea775d28dc286a120675aa32 5.0.0
+4074fcefe73deec0b4dcd8b2e29f6c3c8eade611 5.2.0
+2d6d3a648c1dc1711adcc7bd3ff0930949b3076a 5.4.0
+513d2f77dc3bb1f5335af4faf233ccaa1711ac82 5.6.0
+e013e9930e2f27e5205b93f39d49f3604ae07901 5.8.0
+031cd536bd9f4e89345876c2d438626c57fbb33c 6.0.0
+ca49dcd87a916c997b5fe8bfc79c3195766bd4be 6.0.1
+a8121f01d452fd877749cf304dd691618553fccb 6.0.2
+90177ab7f46ac9703f9130f36f87d696774506be 6.0.3
+c1b6c469704c89d22f838a7c387f8ce2db6c04e8 6.0.4
+d37b211c91cc908fb3dbdc3ea8b52af50ef325a2 6.0.5
+76abf46e661d7f2b5d95bf13fddf107f31d47105 6.0.6
+722e116a59f5a1ba61aede38920e03a05a7d7ce4 6.0.7
+262af554d965998030b6b5b9441bc9469dab2311 6.0.8
+caafedc5aba43c65c49eeddf28ee41d8d35d2108 6.0.9
+a29aac850db9e9d55cc33f3ef1fff7af05043fc8 6.0.10
+c1f4671012217e27ee6fedb519da6eecb6b164d8 6.0.11
+2d40a627f2215c62800149562d033a18d550eabc 6.0.12
+31cb176af93153cff7f9b0ad024ce2d3ecac44c1 6.0.13
+440b2885e1659393f469358808a8b83ccb08c12c 6.0.14
+845114a2dc0253d6cb66883089996adffed70188 6.0.15
+89d9b0b02548363e7dfc984e594d09eff061e29c 6.0.16
+f95deaf6c5404e617fe522d98520c1ba829c64b3 6.0.17
+056876b48b55163bfb276ed713fbc8d3c7ec3209 6.0.18
+3fc2510a1f70752c9489a924727c11951eb5171f 6.0.19
+1491afc63d061116473998f40335b1901c62f7c8 6.0.20
+8f4439b7d4a4908f44c9e6d93b3a45fd8fd39350 6.0.21
+0263312e30f850a6637f1c920dbff1c929bf9ded 6.0.22
+5b249c3acf2243f294089418a325bbdcab7c57ad 6.0.23
+7fd7e8a5ae84dab88f322e900d26cbd2e7115292 6.0.24
+0000000000000000000000000000000000000000 3.8.0
+c835144ba5fd172c9aa8714683e69ca60bc894bc sao-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+20712dc81634af43f9e0ebb41b0dcea50ccffdd2 sao-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+963504a30207af5877c11368ac5dadbc5d28fee1 sao-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+1fea2b717dd8d57d0a233c19d7f1eec35bdde0fa sao-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+16a536201007ba66d36040a34e79a8998c4051f8 sao-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+08b000057b0b5a69f4034f2e1f5c7f2f7f23276f sao-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+47edb75271514576ea775d28dc286a120675aa32 sao-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+4074fcefe73deec0b4dcd8b2e29f6c3c8eade611 sao-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+2d6d3a648c1dc1711adcc7bd3ff0930949b3076a sao-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+513d2f77dc3bb1f5335af4faf233ccaa1711ac82 sao-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+e013e9930e2f27e5205b93f39d49f3604ae07901 sao-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+031cd536bd9f4e89345876c2d438626c57fbb33c sao-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+ca49dcd87a916c997b5fe8bfc79c3195766bd4be sao-6.0.1
+0000000000000000000000000000000000000000 6.0.10
+a29aac850db9e9d55cc33f3ef1fff7af05043fc8 sao-6.0.10
+0000000000000000000000000000000000000000 6.0.11
+c1f4671012217e27ee6fedb519da6eecb6b164d8 sao-6.0.11
+0000000000000000000000000000000000000000 6.0.12
+2d40a627f2215c62800149562d033a18d550eabc sao-6.0.12
+0000000000000000000000000000000000000000 6.0.13
+31cb176af93153cff7f9b0ad024ce2d3ecac44c1 sao-6.0.13
+0000000000000000000000000000000000000000 6.0.14
+440b2885e1659393f469358808a8b83ccb08c12c sao-6.0.14
+0000000000000000000000000000000000000000 6.0.15
+845114a2dc0253d6cb66883089996adffed70188 sao-6.0.15
+0000000000000000000000000000000000000000 6.0.16
+89d9b0b02548363e7dfc984e594d09eff061e29c sao-6.0.16
+0000000000000000000000000000000000000000 6.0.17
+f95deaf6c5404e617fe522d98520c1ba829c64b3 sao-6.0.17
+0000000000000000000000000000000000000000 6.0.18
+056876b48b55163bfb276ed713fbc8d3c7ec3209 sao-6.0.18
+0000000000000000000000000000000000000000 6.0.19
+3fc2510a1f70752c9489a924727c11951eb5171f sao-6.0.19
+0000000000000000000000000000000000000000 6.0.2
+a8121f01d452fd877749cf304dd691618553fccb sao-6.0.2
+0000000000000000000000000000000000000000 6.0.20
+1491afc63d061116473998f40335b1901c62f7c8 sao-6.0.20
+0000000000000000000000000000000000000000 6.0.21
+8f4439b7d4a4908f44c9e6d93b3a45fd8fd39350 sao-6.0.21
+0000000000000000000000000000000000000000 6.0.22
+0263312e30f850a6637f1c920dbff1c929bf9ded sao-6.0.22
+0000000000000000000000000000000000000000 6.0.23
+5b249c3acf2243f294089418a325bbdcab7c57ad sao-6.0.23
+0000000000000000000000000000000000000000 6.0.24
+7fd7e8a5ae84dab88f322e900d26cbd2e7115292 sao-6.0.24
+0000000000000000000000000000000000000000 6.0.3
+90177ab7f46ac9703f9130f36f87d696774506be sao-6.0.3
+0000000000000000000000000000000000000000 6.0.4
+c1b6c469704c89d22f838a7c387f8ce2db6c04e8 sao-6.0.4
+0000000000000000000000000000000000000000 6.0.5
+d37b211c91cc908fb3dbdc3ea8b52af50ef325a2 sao-6.0.5
+0000000000000000000000000000000000000000 6.0.6
+76abf46e661d7f2b5d95bf13fddf107f31d47105 sao-6.0.6
+0000000000000000000000000000000000000000 6.0.7
+722e116a59f5a1ba61aede38920e03a05a7d7ce4 sao-6.0.7
+0000000000000000000000000000000000000000 6.0.8
+262af554d965998030b6b5b9441bc9469dab2311 sao-6.0.8
+0000000000000000000000000000000000000000 6.0.9
+caafedc5aba43c65c49eeddf28ee41d8d35d2108 sao-6.0.9
+4b876ac37410e223bc3628256425d80398d051c6 4.8.0
+144f9950fba568bd77ac0eecd02c7b0d0a98ba87 5.0.0
+ed88584857aa1b39459a420270c0253d460cfe48 5.2.0
+ed46c63b027820aa6e605bb53040f4ed4d133813 5.4.0
+d2db68823a8225ff32842da4e948c1c01197e5cc 5.6.0
+eeab3256f17f6b9bc111d74d6d7f984e75d70f17 5.8.0
+4d33daf649c10711b782ef82b71e49d1a725ccee 6.0.0
+ed86455b7bf99bb7a18309e809adc54e6a4864d8 6.0.1
+0000000000000000000000000000000000000000 4.8.0
+4b876ac37410e223bc3628256425d80398d051c6 trytond-gis-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+144f9950fba568bd77ac0eecd02c7b0d0a98ba87 trytond-gis-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+ed88584857aa1b39459a420270c0253d460cfe48 trytond-gis-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+ed46c63b027820aa6e605bb53040f4ed4d133813 trytond-gis-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+d2db68823a8225ff32842da4e948c1c01197e5cc trytond-gis-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+eeab3256f17f6b9bc111d74d6d7f984e75d70f17 trytond-gis-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+4d33daf649c10711b782ef82b71e49d1a725ccee trytond-gis-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+ed86455b7bf99bb7a18309e809adc54e6a4864d8 trytond-gis-6.0.1
+951bb3311b916cb893adc5294cfb3216d2940169 1.0.0
+9f5ce178a7c403a0865fd4b6c1a82a911df0e998 1.2.0
+e2d31d59de7745de70199b7c340d78b7c4775723 1.4.0
+64691214959ac19983f3b3d1dbb89ff693d1545c 1.6.0
+af3a6063b46bf8739a513a06400fbb9858b5869b 1.8.0
+3475040a8fe081b1b8609350186b779cc640fba4 2.0.0
+1df3f0702c621e65279af52d1f87725747ae8fa4 2.2.0
+640af4420c8eace7ee59a432bec54692f4808b20 2.4.0
+36530840a1d0dce354bbaa58ec849a409d306d83 2.6.0
+2893d6c3efd6df3759ab09a28ea16773a160118e 2.8.0
+759c6ff44b90b8c685e9d9a4124bcd85af204649 3.0.0
+b3d84aa2c3a6ed3b76bfb58a40fc687f68824c29 3.2.0
+f0a9fa755cde703be54efd747f376b096ea8316c 3.4.0
+1e4ba5a3f16e6afd59131b49841eb004e706619d 3.6.0
+0a29a9dba7f56fbdef067772eea3f037934999c9 3.8.0
+49a0caa894dae201ed4ec707f277d9859b5521fb 4.0.0
+eb8949ec5bcedba563b5799f1315a0cecfee61f6 4.2.0
+f253ce8ed02f4c46a69b8b2f29311ad8959698be 4.4.0
+e7a6d0e8002237f624ecc8a45a6a472f7baadd08 4.6.0
+321b0104a732419c11aa4926785502e0615b89c9 4.8.0
+e8cadc044d59edff8a2ca73932c723d507517ec8 5.0.0
+ef34e4e92d45ab2b2b2956bbb6aa8b3ca259eb13 5.2.0
+32170ff46339dbc8609225791ab737a0f3eb0291 5.4.0
+5738a22adbb45390167194e4089adc46c6b638a3 5.6.0
+e21456b81972188261b8a6fc876afc18f641c85b 5.8.0
+2164597ffa5f4f0443e4804944b30f5491070990 6.0.0
+12a321529713d3e0b04fa94883baf66bddf76b75 6.0.1
+10e05cdf4fb3fcd545d8b47d03df8cfb78b38a76 6.0.2
+7b3f3676cf9d72147edcdb0cd477b77b1af7af9e 6.0.3
+68ea8ab81fc3d67e3efe925c3694644a46b7c111 6.0.4
+133f0b26de213e0b5668ae891e176de8f79025fc 6.0.5
+1719ce1a669285333424c02c9e3e45fd636b995c 6.0.6
+67fe2d478dbf413873c0919cfd5d6f3f6f4c2275 6.0.7
+ed230154446f04aed350b7fd0d416a80145645ee 6.0.8
+df3e4a189c249ff54e118a4943c03ecaab5fee60 6.0.9
+32f14e8a77caa727f2dfcec93aa15e05f45ad0b6 6.0.10
+6cb11cbdabc3b93d9698fc17653e8cb24b8154d4 6.0.11
+f34c0f0f0010e95ba38d3378f2e86d7e7a966516 6.0.12
+c1e2890b414d6d9c0bae303577fb784c8e8c715b 6.0.13
+78607bfea2986fe335c98a6ef87905cded1f5b2d 6.0.14
+26fba5237ff3ec9aed2d8aebdc1115e595e76c0b 6.0.15
+c4cf78bb401aec7d52a007eb7bda958658edaf95 6.0.16
+246ad7a2addde11a7b3e828ace36cf5fb1bc6696 6.0.17
+ca63f7f2eef1998de82eae76636c00211f0e3a80 6.0.18
+2973349bc8ca02dc5aba4fbf6a2e4ba270af9780 6.0.19
+9d5f5e04e2b0105bf4ca644bf98811d267054514 6.0.20
+221212011e95b8004c93ec76d21f5ffa12e866ee 6.0.21
+3ff41e43408ddccd4ce91170b6536a2f5cd0112b 6.0.22
+7d81a0faef794489d63fb164d0fb2ba2196c878b 6.0.23
+9b36d68c67a48a2203ef3598f5379d708781dc9e 6.0.24
+0000000000000000000000000000000000000000 1.0.0
+951bb3311b916cb893adc5294cfb3216d2940169 trytond-1.0.0
+0000000000000000000000000000000000000000 1.2.0
+9f5ce178a7c403a0865fd4b6c1a82a911df0e998 trytond-1.2.0
+0000000000000000000000000000000000000000 1.4.0
+e2d31d59de7745de70199b7c340d78b7c4775723 trytond-1.4.0
+0000000000000000000000000000000000000000 1.6.0
+64691214959ac19983f3b3d1dbb89ff693d1545c trytond-1.6.0
+0000000000000000000000000000000000000000 1.8.0
+af3a6063b46bf8739a513a06400fbb9858b5869b trytond-1.8.0
+0000000000000000000000000000000000000000 2.0.0
+3475040a8fe081b1b8609350186b779cc640fba4 trytond-2.0.0
+0000000000000000000000000000000000000000 2.2.0
+1df3f0702c621e65279af52d1f87725747ae8fa4 trytond-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+640af4420c8eace7ee59a432bec54692f4808b20 trytond-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+36530840a1d0dce354bbaa58ec849a409d306d83 trytond-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+2893d6c3efd6df3759ab09a28ea16773a160118e trytond-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+759c6ff44b90b8c685e9d9a4124bcd85af204649 trytond-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+b3d84aa2c3a6ed3b76bfb58a40fc687f68824c29 trytond-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+f0a9fa755cde703be54efd747f376b096ea8316c trytond-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+1e4ba5a3f16e6afd59131b49841eb004e706619d trytond-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+0a29a9dba7f56fbdef067772eea3f037934999c9 trytond-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+49a0caa894dae201ed4ec707f277d9859b5521fb trytond-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+eb8949ec5bcedba563b5799f1315a0cecfee61f6 trytond-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+f253ce8ed02f4c46a69b8b2f29311ad8959698be trytond-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+e7a6d0e8002237f624ecc8a45a6a472f7baadd08 trytond-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+321b0104a732419c11aa4926785502e0615b89c9 trytond-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+e8cadc044d59edff8a2ca73932c723d507517ec8 trytond-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+ef34e4e92d45ab2b2b2956bbb6aa8b3ca259eb13 trytond-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+32170ff46339dbc8609225791ab737a0f3eb0291 trytond-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+5738a22adbb45390167194e4089adc46c6b638a3 trytond-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+e21456b81972188261b8a6fc876afc18f641c85b trytond-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+2164597ffa5f4f0443e4804944b30f5491070990 trytond-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+12a321529713d3e0b04fa94883baf66bddf76b75 trytond-6.0.1
+0000000000000000000000000000000000000000 6.0.10
+32f14e8a77caa727f2dfcec93aa15e05f45ad0b6 trytond-6.0.10
+0000000000000000000000000000000000000000 6.0.11
+6cb11cbdabc3b93d9698fc17653e8cb24b8154d4 trytond-6.0.11
+0000000000000000000000000000000000000000 6.0.12
+f34c0f0f0010e95ba38d3378f2e86d7e7a966516 trytond-6.0.12
+0000000000000000000000000000000000000000 6.0.13
+c1e2890b414d6d9c0bae303577fb784c8e8c715b trytond-6.0.13
+0000000000000000000000000000000000000000 6.0.14
+78607bfea2986fe335c98a6ef87905cded1f5b2d trytond-6.0.14
+0000000000000000000000000000000000000000 6.0.15
+26fba5237ff3ec9aed2d8aebdc1115e595e76c0b trytond-6.0.15
+0000000000000000000000000000000000000000 6.0.16
+c4cf78bb401aec7d52a007eb7bda958658edaf95 trytond-6.0.16
+0000000000000000000000000000000000000000 6.0.17
+246ad7a2addde11a7b3e828ace36cf5fb1bc6696 trytond-6.0.17
+0000000000000000000000000000000000000000 6.0.18
+ca63f7f2eef1998de82eae76636c00211f0e3a80 trytond-6.0.18
+0000000000000000000000000000000000000000 6.0.19
+2973349bc8ca02dc5aba4fbf6a2e4ba270af9780 trytond-6.0.19
+0000000000000000000000000000000000000000 6.0.2
+10e05cdf4fb3fcd545d8b47d03df8cfb78b38a76 trytond-6.0.2
+0000000000000000000000000000000000000000 6.0.20
+9d5f5e04e2b0105bf4ca644bf98811d267054514 trytond-6.0.20
+0000000000000000000000000000000000000000 6.0.21
+221212011e95b8004c93ec76d21f5ffa12e866ee trytond-6.0.21
+0000000000000000000000000000000000000000 6.0.22
+3ff41e43408ddccd4ce91170b6536a2f5cd0112b trytond-6.0.22
+0000000000000000000000000000000000000000 6.0.23
+7d81a0faef794489d63fb164d0fb2ba2196c878b trytond-6.0.23
+0000000000000000000000000000000000000000 6.0.24
+9b36d68c67a48a2203ef3598f5379d708781dc9e trytond-6.0.24
+0000000000000000000000000000000000000000 6.0.3
+7b3f3676cf9d72147edcdb0cd477b77b1af7af9e trytond-6.0.3
+0000000000000000000000000000000000000000 6.0.4
+68ea8ab81fc3d67e3efe925c3694644a46b7c111 trytond-6.0.4
+0000000000000000000000000000000000000000 6.0.5
+133f0b26de213e0b5668ae891e176de8f79025fc trytond-6.0.5
+0000000000000000000000000000000000000000 6.0.6
+1719ce1a669285333424c02c9e3e45fd636b995c trytond-6.0.6
+0000000000000000000000000000000000000000 6.0.7
+67fe2d478dbf413873c0919cfd5d6f3f6f4c2275 trytond-6.0.7
+0000000000000000000000000000000000000000 6.0.8
+ed230154446f04aed350b7fd0d416a80145645ee trytond-6.0.8
+0000000000000000000000000000000000000000 6.0.9
+df3e4a189c249ff54e118a4943c03ecaab5fee60 trytond-6.0.9
+923b3073b686c6dc5a921d0561c934adf3eeb879 1.0.0
+9a6434d63616f46520b6fa1b2be31906ff6f4f6a 1.2.0
+de84c1443d4bad908eabb99c381578b988c2117f 1.4.0
+3976912b23dfc5db2525d4c2e55d17f099d9492d 1.6.0
+8aa050da53cbdfabe1353a37da91c011b7fff549 1.8.0
+1876747b3f963ba6011b4bcf6968e681624c6e24 2.0.0
+5364f8ca6cd2cccfb5f7f39c69bb20bae2b5f495 2.2.0
+fe15da23f7333e0e90d2693aadddf6bed53ed602 2.4.0
+d069f9f1cdc3fea39e71415268582abb2b333ea1 2.6.0
+21fbf8bd36096d926c472acfecd3963369204af8 2.8.0
+9197d90b0257ec7c4576a38092fd592b3e87f7a4 3.0.0
+9721289ee2fdaab8b990de9327c3eab87363c88c 3.2.0
+8e5cba59137f9d0a063fa6d7855e09a51bc5889c 3.4.0
+1ac658605d42d82988cfb232afebfe359aba7776 3.6.0
+f4b9c8a84cf61935afec788c3c639982731452ef 3.8.0
+44d8ed92ff90cf98c4c01d37f12d344d9d66e7f1 4.0.0
+94037cff031ef897c9533a579356977c8a3cf695 4.2.0
+763dc5d8e1d7e483edbf9977dd96afc114555555 4.4.0
+d7ed5002e47249d019fbc546f6ac562db44c54f2 4.6.0
+2a2b48ea60fbb5201ea12443000d3a106a2fa27c 4.8.0
+70ade9d1fe04c734fe2dd7d35dd75b13f1af902a 5.0.0
+d5652dee45a43fccc1f33d39050af6e00e073273 5.2.0
+9f3fbb33df184ca19350cc71054dd9afd55f1cb7 5.4.0
+45d3f736ffb4396ee0dac0e2110fcc17237337ba 5.6.0
+df9de0ceeb27b38b165da7526accad0ad6682e86 5.8.0
+d18d03f0c540dfd2aae81dfaac048378a62c0ef6 6.0.0
+6cdc11292120ffb6df929721adfd738c4916a594 6.0.1
+23df79b60988e64c45be329933d803e0add7ae20 6.0.2
+5ab29a68b330847559add6204df341aeec38c223 6.0.3
+4493ce6c2dfcebde5701360689c635dbd6253d10 6.0.4
+0a8b71b80cc781e88f6a5a7467be9cdb3de50562 6.0.5
+be96d41f66a7bae727ecaf072a955ada01cd73e8 6.0.6
+0d0d910f4edfa1e493d8526dea6bc5f2ad78c30e 6.0.7
+1f7935eb899fb1ccdd363a085ba3a7a24349a3be 6.0.8
+375c664763440caa3f3ac4cdff2f694eedab7f7c 6.0.9
+a8f9ff596e3476c4fa73002524a78011b62b7fb8 6.0.10
+986c3f1e1d5ee57983adf34bf84c348abdba5b42 6.0.11
+e2cd492204aa2dd300eaaad5caca022b0114bea2 6.0.12
+1cbf7901b52d92f993ceba19357085386475c08d 6.0.13
+1474928ec1aa12d34e5fd79675bb284890bafd76 6.0.14
+0ee8d81a43a12bdd3064bcc100ef4a35ab86bfdb 6.0.15
+d1f3f6a0d5722332cddd2cb0837a266e5f5a1881 6.0.16
+dd458525c9d4c92eca991b832f06ec46a639c3a4 6.0.17
+40c3f7f6d487197698ac644ef28fa09dbce7e46a 6.0.18
+bd7b3f9a3c2f9cda09c2d5f89ed1c1f92dd78ff3 6.0.19
+4b448222df4c6c37b0223112c0c2c14f09675d34 6.0.20
+90566b9897fd57fb36e427dd7d8f2bbfa0a9ed5f 6.0.21
+0000000000000000000000000000000000000000 1.0.0
+923b3073b686c6dc5a921d0561c934adf3eeb879 tryton-1.0.0
+0000000000000000000000000000000000000000 1.2.0
+9a6434d63616f46520b6fa1b2be31906ff6f4f6a tryton-1.2.0
+0000000000000000000000000000000000000000 1.4.0
+de84c1443d4bad908eabb99c381578b988c2117f tryton-1.4.0
+0000000000000000000000000000000000000000 1.6.0
+3976912b23dfc5db2525d4c2e55d17f099d9492d tryton-1.6.0
+0000000000000000000000000000000000000000 1.8.0
+8aa050da53cbdfabe1353a37da91c011b7fff549 tryton-1.8.0
+0000000000000000000000000000000000000000 2.0.0
+1876747b3f963ba6011b4bcf6968e681624c6e24 tryton-2.0.0
+0000000000000000000000000000000000000000 2.2.0
+5364f8ca6cd2cccfb5f7f39c69bb20bae2b5f495 tryton-2.2.0
+0000000000000000000000000000000000000000 2.4.0
+fe15da23f7333e0e90d2693aadddf6bed53ed602 tryton-2.4.0
+0000000000000000000000000000000000000000 2.6.0
+d069f9f1cdc3fea39e71415268582abb2b333ea1 tryton-2.6.0
+0000000000000000000000000000000000000000 2.8.0
+21fbf8bd36096d926c472acfecd3963369204af8 tryton-2.8.0
+0000000000000000000000000000000000000000 3.0.0
+9197d90b0257ec7c4576a38092fd592b3e87f7a4 tryton-3.0.0
+0000000000000000000000000000000000000000 3.2.0
+9721289ee2fdaab8b990de9327c3eab87363c88c tryton-3.2.0
+0000000000000000000000000000000000000000 3.4.0
+8e5cba59137f9d0a063fa6d7855e09a51bc5889c tryton-3.4.0
+0000000000000000000000000000000000000000 3.6.0
+1ac658605d42d82988cfb232afebfe359aba7776 tryton-3.6.0
+0000000000000000000000000000000000000000 3.8.0
+f4b9c8a84cf61935afec788c3c639982731452ef tryton-3.8.0
+0000000000000000000000000000000000000000 4.0.0
+44d8ed92ff90cf98c4c01d37f12d344d9d66e7f1 tryton-4.0.0
+0000000000000000000000000000000000000000 4.2.0
+94037cff031ef897c9533a579356977c8a3cf695 tryton-4.2.0
+0000000000000000000000000000000000000000 4.4.0
+763dc5d8e1d7e483edbf9977dd96afc114555555 tryton-4.4.0
+0000000000000000000000000000000000000000 4.6.0
+d7ed5002e47249d019fbc546f6ac562db44c54f2 tryton-4.6.0
+0000000000000000000000000000000000000000 4.8.0
+2a2b48ea60fbb5201ea12443000d3a106a2fa27c tryton-4.8.0
+0000000000000000000000000000000000000000 5.0.0
+70ade9d1fe04c734fe2dd7d35dd75b13f1af902a tryton-5.0.0
+0000000000000000000000000000000000000000 5.2.0
+d5652dee45a43fccc1f33d39050af6e00e073273 tryton-5.2.0
+0000000000000000000000000000000000000000 5.4.0
+9f3fbb33df184ca19350cc71054dd9afd55f1cb7 tryton-5.4.0
+0000000000000000000000000000000000000000 5.6.0
+45d3f736ffb4396ee0dac0e2110fcc17237337ba tryton-5.6.0
+0000000000000000000000000000000000000000 5.8.0
+df9de0ceeb27b38b165da7526accad0ad6682e86 tryton-5.8.0
+0000000000000000000000000000000000000000 6.0.0
+d18d03f0c540dfd2aae81dfaac048378a62c0ef6 tryton-6.0.0
+0000000000000000000000000000000000000000 6.0.1
+6cdc11292120ffb6df929721adfd738c4916a594 tryton-6.0.1
+0000000000000000000000000000000000000000 6.0.10
+a8f9ff596e3476c4fa73002524a78011b62b7fb8 tryton-6.0.10
+0000000000000000000000000000000000000000 6.0.11
+986c3f1e1d5ee57983adf34bf84c348abdba5b42 tryton-6.0.11
+0000000000000000000000000000000000000000 6.0.12
+e2cd492204aa2dd300eaaad5caca022b0114bea2 tryton-6.0.12
+0000000000000000000000000000000000000000 6.0.13
+1cbf7901b52d92f993ceba19357085386475c08d tryton-6.0.13
+0000000000000000000000000000000000000000 6.0.14
+1474928ec1aa12d34e5fd79675bb284890bafd76 tryton-6.0.14
+0000000000000000000000000000000000000000 6.0.15
+0ee8d81a43a12bdd3064bcc100ef4a35ab86bfdb tryton-6.0.15
+0000000000000000000000000000000000000000 6.0.16
+d1f3f6a0d5722332cddd2cb0837a266e5f5a1881 tryton-6.0.16
+0000000000000000000000000000000000000000 6.0.17
+dd458525c9d4c92eca991b832f06ec46a639c3a4 tryton-6.0.17
+0000000000000000000000000000000000000000 6.0.18
+40c3f7f6d487197698ac644ef28fa09dbce7e46a tryton-6.0.18
+0000000000000000000000000000000000000000 6.0.19
+bd7b3f9a3c2f9cda09c2d5f89ed1c1f92dd78ff3 tryton-6.0.19
+0000000000000000000000000000000000000000 6.0.2
+23df79b60988e64c45be329933d803e0add7ae20 tryton-6.0.2
+0000000000000000000000000000000000000000 6.0.20
+4b448222df4c6c37b0223112c0c2c14f09675d34 tryton-6.0.20
+0000000000000000000000000000000000000000 6.0.21
+90566b9897fd57fb36e427dd7d8f2bbfa0a9ed5f tryton-6.0.21
+0000000000000000000000000000000000000000 6.0.3
+5ab29a68b330847559add6204df341aeec38c223 tryton-6.0.3
+0000000000000000000000000000000000000000 6.0.4
+4493ce6c2dfcebde5701360689c635dbd6253d10 tryton-6.0.4
+0000000000000000000000000000000000000000 6.0.5
+0a8b71b80cc781e88f6a5a7467be9cdb3de50562 tryton-6.0.5
+0000000000000000000000000000000000000000 6.0.6
+be96d41f66a7bae727ecaf072a955ada01cd73e8 tryton-6.0.6
+0000000000000000000000000000000000000000 6.0.7
+0d0d910f4edfa1e493d8526dea6bc5f2ad78c30e tryton-6.0.7
+0000000000000000000000000000000000000000 6.0.8
+1f7935eb899fb1ccdd363a085ba3a7a24349a3be tryton-6.0.8
+0000000000000000000000000000000000000000 6.0.9
+375c664763440caa3f3ac4cdff2f694eedab7f7c tryton-6.0.9
diff --git a/modules/stock_split/.drone.yml b/modules/stock_split/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/stock_split/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/stock_split/.flake8 b/modules/stock_split/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/stock_split/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/stock_split/CHANGELOG b/modules/stock_split/CHANGELOG
new file mode 100644
index 0000000000..abf30a58b9
--- /dev/null
+++ b/modules/stock_split/CHANGELOG
@@ -0,0 +1,61 @@
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+* Add shipment split
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.4.0 - 2012-07-31
+* Initial release
diff --git a/modules/stock_split/COPYRIGHT b/modules/stock_split/COPYRIGHT
new file mode 100644
index 0000000000..4acb8540a3
--- /dev/null
+++ b/modules/stock_split/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2012-2021 Cédric Krier.
+Copyright (C) 2012-2021 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/stock_split/LICENSE b/modules/stock_split/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/stock_split/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/stock_split/MANIFEST.in b/modules/stock_split/MANIFEST.in
new file mode 100644
index 0000000000..94499680c6
--- /dev/null
+++ b/modules/stock_split/MANIFEST.in
@@ -0,0 +1,6 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
+include icons/LICENSE
diff --git a/modules/stock_split/README.rst b/modules/stock_split/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/stock_split/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/stock_split/__init__.py b/modules/stock_split/__init__.py
new file mode 100644
index 0000000000..5eaf73020b
--- /dev/null
+++ b/modules/stock_split/__init__.py
@@ -0,0 +1,20 @@
+# 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 stock
+
+
+def register():
+ Pool.register(
+ stock.Move,
+ stock.SplitMoveStart,
+ stock.ShipmentInReturn,
+ stock.ShipmentOut,
+ stock.ShipmentInternal,
+ stock.SplitShipmentStart,
+ module='stock_split', type_='model')
+ Pool.register(
+ stock.SplitMove,
+ stock.SplitShipment,
+ module='stock_split', type_='wizard')
diff --git a/modules/stock_split/doc/index.rst b/modules/stock_split/doc/index.rst
new file mode 100644
index 0000000000..7b4647aff3
--- /dev/null
+++ b/modules/stock_split/doc/index.rst
@@ -0,0 +1,11 @@
+Stock Split Module
+##################
+
+The Stock Split module adds on the stock move a wizard that allows to split them.
+The move is split into moves of *Quantity*. If *Counts* is set, it will be
+split only this number of times. On occasion there can be a move with the
+remaining quantity.
+
+It also adds on *Customer*, *Supplier Return* and *Internal* shipments a wizard
+that allows to split them. The selected moves of the shipment are shifted to a
+new copy of the shipment.
diff --git a/modules/stock_split/icons/LICENSE b/modules/stock_split/icons/LICENSE
new file mode 100644
index 0000000000..7a4a3ea242
--- /dev/null
+++ b/modules/stock_split/icons/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/modules/stock_split/icons/tryton-stock-split.svg b/modules/stock_split/icons/tryton-stock-split.svg
new file mode 100644
index 0000000000..d64516e11e
--- /dev/null
+++ b/modules/stock_split/icons/tryton-stock-split.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/stock_split/locale/bg.po b/modules/stock_split/locale/bg.po
new file mode 100644
index 0000000000..81c50c472a
--- /dev/null
+++ b/modules/stock_split/locale/bg.po
@@ -0,0 +1,106 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Количество"
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Десетични единици"
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Мерни единици"
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Категория мер. ед."
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Отказване"
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Отказване"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/ca.po b/modules/stock_split/locale/ca.po
new file mode 100644
index 0000000000..a43f4d5c62
--- /dev/null
+++ b/modules/stock_split/locale/ca.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Número"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Quantitat"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Dígits de la unitat"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "UdM"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Categoria de la UdM"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "Moviments del domini"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Moviments"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "El número màxim de moviments."
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr "Els moviments seleccionats s'enviaran en un albarà nou."
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Divideix moviments"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Divideix albarà"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Divideix"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Divideix"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Divideix"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Divideix"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Divisió de moviments"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Divideix albarà"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Divideix"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr "Trieu els moviments a enviar en un albarà nou:"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Cancel·la"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Divideix"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Cancel·la"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Divideix"
diff --git a/modules/stock_split/locale/cs.po b/modules/stock_split/locale/cs.po
new file mode 100644
index 0000000000..dadd5887e7
--- /dev/null
+++ b/modules/stock_split/locale/cs.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/de.po b/modules/stock_split/locale/de.po
new file mode 100644
index 0000000000..8e3b86ea90
--- /dev/null
+++ b/modules/stock_split/locale/de.po
@@ -0,0 +1,98 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Anzahl"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Menge"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Nachkommastellen"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Einheit"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Maßeinheitenkategorie"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "Wertebereich Lagerbewegungen (Domain)"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Lagerbewegungen"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "Maximale Anzahl von Lagerbewegungen."
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+"Die gewählten Lagerbewegungen werden in die neue Lieferung übernommen."
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Lagerbewegung aufteilen"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Lieferung aufteilen"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Aufteilen"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Aufteilen"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Aufteilen"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Aufteilen"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Lagerbewegung Aufteilung"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Lieferung aufteilen"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Aufteilen"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+"Auswahl der Lagerbewegungen, die in die neue Lieferung übernommen werden "
+"sollen:"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Aufteilen"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Aufteilen"
diff --git a/modules/stock_split/locale/es.po b/modules/stock_split/locale/es.po
new file mode 100644
index 0000000000..7b09481ae6
--- /dev/null
+++ b/modules/stock_split/locale/es.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Contador"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Cantidad"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Dígitos de la unidad"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "UdM"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Categoría UdM"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "Movimientos del dominio"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Movimientos"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "El número límite de movimientos."
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr "Los movimientos seleccionados se enviarán en un albarán nuevo."
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Dividir movimiento"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Dividir albarán"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Dividir movimiento"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Dividir albarán"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr "Selecciona los movimientos a enviar en un albarán nuevo:"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Dividir"
diff --git a/modules/stock_split/locale/es_419.po b/modules/stock_split/locale/es_419.po
new file mode 100644
index 0000000000..3579f13ec8
--- /dev/null
+++ b/modules/stock_split/locale/es_419.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Categoría de UdM"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr ""
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr ""
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr ""
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr ""
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr ""
diff --git a/modules/stock_split/locale/et.po b/modules/stock_split/locale/et.po
new file mode 100644
index 0000000000..7f0a7b3655
--- /dev/null
+++ b/modules/stock_split/locale/et.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Loendused"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Kogus"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Ühiku numbriväärtus"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Mõõtühik"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Mõõtühiku kategooria"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Liikumised"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Jaga liikumine"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Jaga lähetus"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Jaga"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Jaga"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Jaga"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Jaga"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Jaga liikumine"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Jaga lähetus"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Jaga"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Tühista"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Jaga"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Tühista"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Jaga"
diff --git a/modules/stock_split/locale/fa.po b/modules/stock_split/locale/fa.po
new file mode 100644
index 0000000000..53c5a7dd69
--- /dev/null
+++ b/modules/stock_split/locale/fa.po
@@ -0,0 +1,97 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "شمارش"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "مقدار/تعداد"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "ارقام واحد"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "واحد اندازی گیری"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "دسته بندی واحد اندازه گیری"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "دامنه جابجایی ها"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "جابجایی ها"
+
+#, fuzzy
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "تعداد محدود جابجایی ها"
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr "جابجایی های انتخاب شده در محموله جدید ارسال خواهند شد."
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "جداسازی جابجایی"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "جداسازی محموله"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "جداسازی جابجایی"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "جداسازی محموله"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "جداسازی"
+
+#, fuzzy
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr "انتخاب جابجایی برای ارسال در محموله جدید"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "انصراف"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "جداسازی"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "انصراف"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "جداسازی"
diff --git a/modules/stock_split/locale/fi.po b/modules/stock_split/locale/fi.po
new file mode 100644
index 0000000000..dadd5887e7
--- /dev/null
+++ b/modules/stock_split/locale/fi.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/fr.po b/modules/stock_split/locale/fr.po
new file mode 100644
index 0000000000..7b8ca69b47
--- /dev/null
+++ b/modules/stock_split/locale/fr.po
@@ -0,0 +1,96 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Décompte"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Quantité"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Décimales de l'unité"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Unité de mesure"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Catégorie d'unité de mesure"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "Domaine des mouvements"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Mouvements"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "Le nombre limite de mouvements."
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+"Les mouvements sélectionnés seront envoyés sur la nouvelle expédition."
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Diviser le mouvement"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Diviser l'expédition"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Diviser"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Diviser"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Diviser"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Diviser"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Diviser le mouvement"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Diviser l'expédition"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Diviser"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr "Choisir les mouvements à envoyer sur la nouvelle expédition :"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Annuler"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Diviser"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Annuler"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Diviser"
diff --git a/modules/stock_split/locale/hu.po b/modules/stock_split/locale/hu.po
new file mode 100644
index 0000000000..c68630d0dd
--- /dev/null
+++ b/modules/stock_split/locale/hu.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Mennyiség"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Mennyiség"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Tizedes vessző utáni számhelyek"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Egység"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Egység kategória"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+#, fuzzy
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "Raktár mozgatás max. mennyisége"
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Raktár mozgatás beosztása"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Beosztás"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Mégse"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Beosztás"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Mégse"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Beosztás"
diff --git a/modules/stock_split/locale/id.po b/modules/stock_split/locale/id.po
new file mode 100644
index 0000000000..8ea52e0b44
--- /dev/null
+++ b/modules/stock_split/locale/id.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Perpindahan"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr ""
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr ""
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr ""
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Batal"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr ""
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Batal"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr ""
diff --git a/modules/stock_split/locale/it.po b/modules/stock_split/locale/it.po
new file mode 100644
index 0000000000..bf1ddc0393
--- /dev/null
+++ b/modules/stock_split/locale/it.po
@@ -0,0 +1,101 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Quantità"
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Posizioni Unità"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Annulla"
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Annulla"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/lo.po b/modules/stock_split/locale/lo.po
new file mode 100644
index 0000000000..7dc78e7e96
--- /dev/null
+++ b/modules/stock_split/locale/lo.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "ນັບ"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "ຈຳນວນ"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "ຫຼັກໜ່ວຍ"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "ຫົວໜ່ວຍ"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "ໝວດຫົວໜ່ວຍ"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+#, fuzzy
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "ຕົວເລກຈຳກັດຂອງການເຄື່ອນຍ້າຍ"
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "ແຍກການເຄື່ອນຍ້າຍ"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "ແຍກ"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "ຍົກເລີກ"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "ແຍກ"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "ຍົກເລີກ"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "ແຍກ"
diff --git a/modules/stock_split/locale/lt.po b/modules/stock_split/locale/lt.po
new file mode 100644
index 0000000000..dadd5887e7
--- /dev/null
+++ b/modules/stock_split/locale/lt.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/nl.po b/modules/stock_split/locale/nl.po
new file mode 100644
index 0000000000..e539c76050
--- /dev/null
+++ b/modules/stock_split/locale/nl.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "tellingen"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Hoeveelheid"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Decimalen eenheid"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "maateenheid"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "maateenheid categorie"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "Domein boekingen"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Boekingen/bewegingen"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "maximum aantal bewegingen."
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr "De geselecteerde bewegingen worden verzonden in de nieuwe zending."
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split de beweging"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Zending splitsen"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "opsplitsen"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "opsplitsen"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "opsplitsen"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "opsplitsen"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split de beweging"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Zending splitsen"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "opsplitsen"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr "Kies beweging om nieuwe zending te verzenden:"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Annuleren"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "opsplitsen"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Annuleren"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "opsplitsen"
diff --git a/modules/stock_split/locale/pl.po b/modules/stock_split/locale/pl.po
new file mode 100644
index 0000000000..007af64884
--- /dev/null
+++ b/modules/stock_split/locale/pl.po
@@ -0,0 +1,101 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Ilość"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Cyfry jednostki"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Jm"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Kategoria jm"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Ruchy"
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Anuluj"
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Anuluj"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/pt.po b/modules/stock_split/locale/pt.po
new file mode 100644
index 0000000000..96b00d1d42
--- /dev/null
+++ b/modules/stock_split/locale/pt.po
@@ -0,0 +1,99 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Contador"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Quantidade"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Dígitos decimais da unidade"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "UDM"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Categoria da UDM"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr "Domínio de Movimentações"
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr "Movimentos"
+
+#, fuzzy
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "O número limite de movimentações."
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr "As movimentações selecionadas serão enviadas na próxima remessa."
+
+#, fuzzy
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Dividir movimento"
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Dividir Remessa"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Dividir"
+
+#, fuzzy
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr "Escolher movimentações para enviar na nova remessa"
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Dividir"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Dividir"
diff --git a/modules/stock_split/locale/ro.po b/modules/stock_split/locale/ro.po
new file mode 100644
index 0000000000..40ec8bd82b
--- /dev/null
+++ b/modules/stock_split/locale/ro.po
@@ -0,0 +1,95 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr ""
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr ""
+
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr ""
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr ""
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr ""
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr ""
diff --git a/modules/stock_split/locale/ru.po b/modules/stock_split/locale/ru.po
new file mode 100644
index 0000000000..8531ebb513
--- /dev/null
+++ b/modules/stock_split/locale/ru.po
@@ -0,0 +1,106 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Кол-во"
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Группа цифр"
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "Ед.изм."
+
+#, fuzzy
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Категория ед. измерения"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Отменить"
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Отменить"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/sl.po b/modules/stock_split/locale/sl.po
new file mode 100644
index 0000000000..089d7ef33a
--- /dev/null
+++ b/modules/stock_split/locale/sl.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr "Število"
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr "Količina"
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr "Decimalke"
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr "ME"
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr "Kategorija ME"
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+#, fuzzy
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr "Omejitev števila prenosov"
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Delitev prometa"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Delitev"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "Prekliči"
+
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Delitev"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "Prekliči"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Delitev"
diff --git a/modules/stock_split/locale/tr.po b/modules/stock_split/locale/tr.po
new file mode 100644
index 0000000000..dadd5887e7
--- /dev/null
+++ b/modules/stock_split/locale/tr.po
@@ -0,0 +1,100 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/locale/zh_CN.po b/modules/stock_split/locale/zh_CN.po
new file mode 100644
index 0000000000..decce1a601
--- /dev/null
+++ b/modules/stock_split/locale/zh_CN.po
@@ -0,0 +1,102 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:stock.move.split.start,count:"
+msgid "Counts"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,quantity:"
+msgid "Quantity"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom:"
+msgid "Uom"
+msgstr ""
+
+msgctxt "field:stock.move.split.start,uom_category:"
+msgid "Uom Category"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,domain_moves:"
+msgid "Domain Moves"
+msgstr ""
+
+msgctxt "field:stock.shipment.split.start,moves:"
+msgid "Moves"
+msgstr ""
+
+msgctxt "help:stock.move.split.start,count:"
+msgid "The limit number of moves."
+msgstr ""
+
+msgctxt "help:stock.shipment.split.start,moves:"
+msgid "The selected moves will be sent in the new shipment."
+msgstr ""
+
+msgctxt "model:ir.action,name:wizard_split_move"
+msgid "Split Move"
+msgstr "Split Move"
+
+msgctxt "model:ir.action,name:wizard_split_shipment"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+msgctxt "model:ir.model.button,string:move_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_in_return_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_internal_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "model:ir.model.button,string:shipment_out_split_wizard_button"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "model:stock.move.split.start,name:"
+msgid "Split Move"
+msgstr "Split Move"
+
+#, fuzzy
+msgctxt "model:stock.shipment.split.start,name:"
+msgid "Split Shipment"
+msgstr "Split Shipment"
+
+#, fuzzy
+msgctxt "view:stock.move:"
+msgid "Split"
+msgstr "Split"
+
+msgctxt "view:stock.shipment.split.start:"
+msgid "Choose move to send in new shipment:"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,end:"
+msgid "Cancel"
+msgstr "取消"
+
+#, fuzzy
+msgctxt "wizard_button:stock.move.split,start,split:"
+msgid "Split"
+msgstr "Split"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,end:"
+msgid "Cancel"
+msgstr "取消"
+
+#, fuzzy
+msgctxt "wizard_button:stock.shipment.split,start,split:"
+msgid "Split"
+msgstr "Split"
diff --git a/modules/stock_split/setup.py b/modules/stock_split/setup.py
new file mode 100755
index 0000000000..98cdb40eef
--- /dev/null
+++ b/modules/stock_split/setup.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_stock_split'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [get_require_version('proteus')]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to split stock move',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/stock_split',
+ },
+ keywords='tryton stock split',
+ package_dir={'trytond.modules.stock_split': '.'},
+ packages=(
+ ['trytond.modules.stock_split']
+ + ['trytond.modules.stock_split.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.stock_split': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ stock_split = trytond.modules.stock_split
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/stock_split/stock.py b/modules/stock_split/stock.py
new file mode 100644
index 0000000000..66c8e61c98
--- /dev/null
+++ b/modules/stock_split/stock.py
@@ -0,0 +1,209 @@
+# 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.model import ModelView, fields
+from trytond.wizard import Wizard, StateView, StateTransition, Button
+from trytond.pyson import Eval
+from trytond.pool import Pool, PoolMeta
+from trytond.transaction import Transaction
+
+
+class Move(metaclass=PoolMeta):
+ __name__ = 'stock.move'
+
+ @classmethod
+ def __setup__(cls):
+ super(Move, cls).__setup__()
+ cls._buttons.update({
+ 'split_wizard': {
+ 'readonly': ~Eval('state').in_(['draft', 'assigned']),
+ 'depends': ['state'],
+ },
+ })
+
+ @classmethod
+ @ModelView.button_action('stock_split.wizard_split_move')
+ def split_wizard(cls, moves):
+ pass
+
+ def split(self, quantity, uom, count=None):
+ """
+ Split the move into moves of quantity.
+ If count is not defined, the move will be split until the remainder is
+ less than quantity.
+ Return the split moves
+ """
+ pool = Pool()
+ Uom = pool.get('product.uom')
+
+ moves = [self]
+ remainder = Uom.compute_qty(self.uom, self.quantity, uom)
+ if remainder <= quantity:
+ return moves
+ state = self.state
+ self.write([self], {
+ 'state': 'draft',
+ })
+ self.write([self], {
+ 'quantity': quantity,
+ 'uom': uom.id,
+ })
+ remainder -= quantity
+ if count:
+ count -= 1
+ while (remainder > quantity
+ and (count or count is None)):
+ with Transaction().set_context(_stock_move_split=True):
+ moves.extend(self.copy([self], {
+ 'quantity': quantity,
+ 'uom': uom.id,
+ }))
+ remainder -= quantity
+ if count:
+ count -= 1
+ remainder = uom.round(remainder)
+ assert remainder >= 0
+ if remainder:
+ with Transaction().set_context(_stock_move_split=True):
+ moves.extend(self.copy([self], {
+ 'quantity': remainder,
+ 'uom': uom.id,
+ }))
+ self.write(moves, {
+ 'state': state,
+ })
+ return moves
+
+
+class SplitMoveStart(ModelView):
+ 'Split Move'
+ __name__ = 'stock.move.split.start'
+ count = fields.Integer('Counts', help='The limit number of moves.')
+ quantity = fields.Float('Quantity', required=True,
+ digits=(16, Eval('unit_digits', 2)),
+ depends=['unit_digits'])
+ uom = fields.Many2One('product.uom', 'Uom', required=True,
+ domain=[
+ ('category', '=', Eval('uom_category')),
+ ],
+ depends=['uom_category'])
+ unit_digits = fields.Integer('Unit Digits', readonly=True)
+ uom_category = fields.Many2One('product.uom.category', 'Uom Category',
+ readonly=True)
+
+ @fields.depends('uom')
+ def on_change_with_unit_digits(self):
+ if self.uom:
+ return self.uom.digits
+ return 2
+
+
+class SplitMove(Wizard):
+ 'Split Move'
+ __name__ = 'stock.move.split'
+ start = StateView('stock.move.split.start',
+ 'stock_split.split_start_view_form', [
+ Button('Cancel', 'end', 'tryton-cancel'),
+ Button('Split', 'split', 'tryton-ok', default=True),
+ ])
+ split = StateTransition()
+
+ def default_start(self, fields):
+ return {
+ 'uom': self.record.uom.id,
+ 'unit_digits': self.record.unit_digits,
+ 'uom_category': self.record.uom.category.id,
+ }
+
+ def transition_split(self):
+ self.record.split(
+ self.start.quantity, self.start.uom, self.start.count)
+ return 'end'
+
+
+class _ShipmentSplit(ModelView):
+
+ @classmethod
+ def __setup__(cls):
+ super(_ShipmentSplit, cls).__setup__()
+ cls._buttons.update({
+ 'split_wizard': {
+ 'readonly': Eval('state') != 'draft',
+ 'invisible': Eval('state') != 'draft',
+ 'depends': ['state'],
+ },
+ })
+
+ @classmethod
+ @ModelView.button_action('stock_split.wizard_split_shipment')
+ def split_wizard(cls, shipments):
+ pass
+
+
+class ShipmentInReturn(_ShipmentSplit, metaclass=PoolMeta):
+ __name__ = 'stock.shipment.in.return'
+
+
+class ShipmentOut(_ShipmentSplit, metaclass=PoolMeta):
+ __name__ = 'stock.shipment.out'
+
+
+class ShipmentInternal(_ShipmentSplit, metaclass=PoolMeta):
+ __name__ = 'stock.shipment.internal'
+
+
+class SplitShipment(Wizard):
+ "Split Shipment"
+ __name__ = 'stock.shipment.split'
+ start = StateView('stock.shipment.split.start',
+ 'stock_split.shipment_split_start_view_form', [
+ Button("Cancel", 'end', 'tryton-cancel'),
+ Button("Split", 'split', 'tryton-ok', default=True),
+ ])
+ split = StateTransition()
+
+ def get_moves(self, shipment):
+ if shipment.__name__ == 'stock.shipment.out':
+ return shipment.outgoing_moves
+ elif shipment.__name__ in {
+ 'stock.shipment.in.return',
+ 'stock.shipment.internal',
+ }:
+ return shipment.moves
+
+ def default_start(self, fields):
+ moves = self.get_moves(self.record)
+ move_ids = [m.id for m in moves if m.state == 'draft']
+ return {
+ 'domain_moves': move_ids,
+ }
+
+ def transition_split(self):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ shipment = self.record
+ Shipment = self.model
+ if shipment.state != 'draft':
+ raise ValueError("Wrong shipment state")
+ if not set(self.start.moves).issubset(self.start.domain_moves):
+ raise ValueError("Invalid moves, %s != %s" % (self.start.moves,
+ self.start.domain_moves))
+
+ if Shipment.__name__ == 'stock.shipment.out':
+ Move.draft(shipment.inventory_moves)
+ Move.delete(
+ [m for m in shipment.inventory_moves if m.state == 'draft'])
+
+ shipment, = Shipment.copy([shipment], default={'moves': None})
+ Move.write(list(self.start.moves), {'shipment': str(shipment)})
+ return 'end'
+
+
+class SplitShipmentStart(ModelView):
+ "Split Shipment"
+ __name__ = 'stock.shipment.split.start'
+ moves = fields.Many2Many(
+ 'stock.move', None, None, "Moves",
+ domain=[('id', 'in', Eval('domain_moves'))],
+ depends=['domain_moves'],
+ help="The selected moves will be sent in the new shipment.")
+ domain_moves = fields.Many2Many('stock.move', None, None, "Domain Moves")
diff --git a/modules/stock_split/stock.xml b/modules/stock_split/stock.xml
new file mode 100644
index 0000000000..94f1b452e3
--- /dev/null
+++ b/modules/stock_split/stock.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+ tryton-stock-split
+ icons/tryton-stock-split.svg
+
+
+
+ Split Move
+ stock.move.split
+ stock.move
+
+
+
+ stock.move
+
+ move_form
+
+
+
+ stock.move
+
+ move_tree
+
+
+
+ stock.move
+
+ move_tree
+
+
+
+ stock.move
+
+ move_tree
+
+
+
+ stock.move.split.start
+ form
+ split_start_form
+
+
+ split_wizard
+ Split
+
+
+
+
+ stock.shipment.in.return
+
+ shipment_split_form
+
+
+
+ split_wizard
+ Split
+
+
+
+
+ stock.shipment.out
+
+ shipment_split_form
+
+
+
+ split_wizard
+ Split
+
+
+
+
+ stock.shipment.internal
+
+ shipment_split_form
+
+
+
+ split_wizard
+ Split
+
+
+
+
+ Split Shipment
+ stock.shipment.split
+
+
+
+ stock.shipment.split.start
+ form
+ shipment_split_start_form
+
+
+
diff --git a/modules/stock_split/tests/__init__.py b/modules/stock_split/tests/__init__.py
new file mode 100644
index 0000000000..25728613a1
--- /dev/null
+++ b/modules/stock_split/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.stock_split.tests.test_stock_split import suite
+except ImportError:
+ from .test_stock_split import suite
+
+__all__ = ['suite']
diff --git a/modules/stock_split/tests/scenario_stock_split_shipment.rst b/modules/stock_split/tests/scenario_stock_split_shipment.rst
new file mode 100644
index 0000000000..2ed919fc88
--- /dev/null
+++ b/modules/stock_split/tests/scenario_stock_split_shipment.rst
@@ -0,0 +1,95 @@
+====================
+Stock Split Shipment
+====================
+
+Imports::
+
+ >>> from decimal import Decimal
+
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+
+Activate modules::
+
+ >>> config = activate_modules('stock_split')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create customer::
+
+ >>> Party = Model.get('party.party')
+ >>> customer = Party(name="Customer")
+ >>> customer.save()
+
+Create product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> Product = Model.get('product.product')
+ >>> unit, = ProductUom.find([('name', '=', "Unit")])
+ >>> template = ProductTemplate()
+ >>> template.name = "Product"
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> Location = Model.get('stock.location')
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+ >>> output_loc, = Location.find([('code', '=', 'OUT')])
+ >>> storage_loc, = Location.find([('code', '=', 'STO')])
+
+Create shipment with 2 lines::
+
+ >>> Shipment = Model.get('stock.shipment.out')
+ >>> Move = Model.get('stock.move')
+
+ >>> shipment1 = Shipment()
+ >>> shipment1.customer = customer
+
+ >>> for i in range(1, 3):
+ ... move = shipment1.outgoing_moves.new()
+ ... move.product = product
+ ... move.uom = unit
+ ... move.quantity = i
+ ... move.from_location = output_loc
+ ... move.to_location = customer_loc
+ ... move.unit_price = Decimal(1)
+
+ >>> shipment1.save()
+ >>> move1, move2 = shipment1.outgoing_moves
+
+Set to waiting and go back to draft to get inventory moves::
+
+ >>> shipment1.click('wait')
+ >>> shipment1.click('draft')
+ >>> len(shipment1.inventory_moves)
+ 2
+
+Split shipment::
+
+ >>> split_shipment = Wizard('stock.shipment.split', [shipment1])
+ >>> len(split_shipment.form.domain_moves)
+ 2
+ >>> split_shipment.form.moves.append(Move(move2.id))
+ >>> split_shipment.execute('split')
+
+ >>> shipment2, = Shipment.find([('id', '!=', shipment1.id)])
+
+ >>> move, = shipment1.outgoing_moves
+ >>> move.id == move1.id
+ True
+ >>> len(shipment1.inventory_moves)
+ 0
+
+ >>> move, = shipment2.outgoing_moves
+ >>> move.id == move2.id
+ True
diff --git a/modules/stock_split/tests/test_stock_split.py b/modules/stock_split/tests/test_stock_split.py
new file mode 100644
index 0000000000..78b035ad27
--- /dev/null
+++ b/modules/stock_split/tests/test_stock_split.py
@@ -0,0 +1,105 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import doctest
+import unittest
+from decimal import Decimal
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.tests.test_tryton import doctest_teardown
+from trytond.tests.test_tryton import doctest_checker
+from trytond.pool import Pool
+
+from trytond.modules.company.tests import (
+ create_company, set_company, CompanyTestMixin)
+
+
+class StockSplitTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test Stock Lot module'
+ module = 'stock_split'
+
+ @with_transaction()
+ def test_split(self):
+ 'Test split'
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ Location = pool.get('stock.location')
+ Move = pool.get('stock.move')
+
+ unit, = Uom.search([('name', '=', 'Unit')])
+ template, = Template.create([{
+ 'name': 'Test Split',
+ 'type': 'goods',
+ 'default_uom': unit.id,
+ 'list_price': Decimal(0),
+ }])
+ product, = Product.create([{
+ 'template': template.id,
+ }])
+ input_, = Location.search([('code', '=', 'IN')])
+ storage, = Location.search([('code', '=', 'STO')])
+ company = create_company()
+ with set_company(company):
+
+ def create_move(quantity):
+ move, = Move.create([{
+ 'product': product.id,
+ 'uom': unit.id,
+ 'quantity': quantity,
+ 'from_location': input_.id,
+ 'to_location': storage.id,
+ 'company': company.id,
+ }])
+ return move
+
+ move = create_move(10)
+ moves = move.split(5, unit)
+ self.assertEqual(len(moves), 2)
+ self.assertEqual([m.quantity for m in moves], [5, 5])
+
+ move = create_move(13)
+ moves = move.split(5, unit)
+ self.assertEqual(len(moves), 3)
+ self.assertEqual([m.quantity for m in moves], [5, 5, 3])
+
+ move = create_move(7)
+ moves = move.split(8, unit)
+ self.assertEqual(moves, [move])
+ self.assertEqual(move.quantity, 7)
+
+ move = create_move(20)
+ moves = move.split(5, unit, count=2)
+ self.assertEqual(len(moves), 3)
+ self.assertEqual([m.quantity for m in moves], [5, 5, 10])
+
+ move = create_move(20)
+ moves = move.split(5, unit, count=4)
+ self.assertEqual(len(moves), 4)
+ self.assertEqual([m.quantity for m in moves], [5, 5, 5, 5])
+
+ move = create_move(10)
+ moves = move.split(5, unit, count=3)
+ self.assertEqual(len(moves), 2)
+ self.assertEqual([m.quantity for m in moves], [5, 5])
+
+ move = create_move(10)
+ Move.write([move], {
+ 'state': 'assigned',
+ })
+ moves = move.split(5, unit)
+ self.assertEqual(len(moves), 2)
+ self.assertEqual([m.quantity for m in moves], [5, 5])
+ self.assertEqual([m.state for m in moves],
+ ['assigned', 'assigned'])
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ StockSplitTestCase))
+ suite.addTests(doctest.DocFileSuite('scenario_stock_split_shipment.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ return suite
diff --git a/modules/stock_split/tox.ini b/modules/stock_split/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/stock_split/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/stock_split/tryton.cfg b/modules/stock_split/tryton.cfg
new file mode 100644
index 0000000000..add088425d
--- /dev/null
+++ b/modules/stock_split/tryton.cfg
@@ -0,0 +1,7 @@
+[tryton]
+version=6.0.1
+depends:
+ ir
+ stock
+xml:
+ stock.xml
diff --git a/modules/stock_split/view/move_form.xml b/modules/stock_split/view/move_form.xml
new file mode 100644
index 0000000000..5b7e3e3dc4
--- /dev/null
+++ b/modules/stock_split/view/move_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/stock_split/view/move_tree.xml b/modules/stock_split/view/move_tree.xml
new file mode 100644
index 0000000000..e7d5b40807
--- /dev/null
+++ b/modules/stock_split/view/move_tree.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/stock_split/view/shipment_split_form.xml b/modules/stock_split/view/shipment_split_form.xml
new file mode 100644
index 0000000000..be65c958f3
--- /dev/null
+++ b/modules/stock_split/view/shipment_split_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/stock_split/view/shipment_split_start_form.xml b/modules/stock_split/view/shipment_split_start_form.xml
new file mode 100644
index 0000000000..9f3d153e97
--- /dev/null
+++ b/modules/stock_split/view/shipment_split_start_form.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/modules/stock_split/view/split_start_form.xml b/modules/stock_split/view/split_start_form.xml
new file mode 100644
index 0000000000..df7c25c065
--- /dev/null
+++ b/modules/stock_split/view/split_start_form.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/modules/stock_supply/.drone.yml b/modules/stock_supply/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/stock_supply/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/stock_supply/.flake8 b/modules/stock_supply/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/stock_supply/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/stock_supply/CHANGELOG b/modules/stock_supply/CHANGELOG
new file mode 100644
index 0000000000..811f14981d
--- /dev/null
+++ b/modules/stock_supply/CHANGELOG
@@ -0,0 +1,118 @@
+Version 6.0.3 - 2022-06-03
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.2 - 2022-04-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.1 - 2021-07-21
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+* Add cron job to supply stock
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+* Make order point's minimal quantity required
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+* Ceil purchase request quantity
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+* Add warning for late customer moves
+* Change supply period into TimeDelta
+* Add support for overflowing quantities
+* Merge supply into one wizard
+* Improve computation of max lead time
+* Generate request internal shipments recursively
+* Do not create purchase request if other order point exists
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+* Add configuration for the supply period
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Move purchase request to purchase_request module
+* Add Python3 support
+* Add searcher on purchase
+* Adding exception state on the purchase requests
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+* Generate purchase requests even if rounding set the quantity to 0
+* Add searcher on purchase_request's state
+* Add default internal provisioning per location
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+* Add relate from product to order points
+
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+* Allow to calculate purchase requests filtered by warehouse
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+* Add wizard to create internal shipments
+* Add warning for late supplier moves when creating purchase request
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+* find_best_supplier doesn't optimize anymore on the delivery delay
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+* Generate also purchase requests for assets
+
+Version 2.4.0 - 2012-04-24
+* Bug fixes (see mercurial logs for details)
+* Warehouse is not always required
+* get_shortage works on a list of products
+* Group purchases using a key
+
+Version 2.2.0 - 2011-10-25
+* Bug fixes (see mercurial logs for details)
+* Add wizard to create purchase requests
+
+Version 2.0.0 - 2011-04-27
+* Bug fixes (see mercurial logs for details)
+
+Version 1.8.0 - 2010-11-01
+* Bug fixes (see mercurial logs for details)
+
+Version 1.6.0 - 2010-05-13
+* Bug fixes (see mercurial logs for details)
+
+Version 1.4.0 - 2009-10-19
+* Bug fixes (see mercurial logs for details)
+
+Version 1.2.0 - 2009-04-20
+* Bug fixes (see mercurial logs for details)
+* Allow egg installation
+
+Version 1.0.0 - 2008-11-17
+* Initial release
diff --git a/modules/stock_supply/COPYRIGHT b/modules/stock_supply/COPYRIGHT
new file mode 100644
index 0000000000..3e56d98c19
--- /dev/null
+++ b/modules/stock_supply/COPYRIGHT
@@ -0,0 +1,17 @@
+Copyright (C) 2013-2019 NaN-tic.
+Copyright (C) 2008-2022 Cédric Krier.
+Copyright (C) 2008-2013 Bertrand Chenal.
+Copyright (C) 2008-2022 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/stock_supply/LICENSE b/modules/stock_supply/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/stock_supply/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/stock_supply/MANIFEST.in b/modules/stock_supply/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/stock_supply/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/stock_supply/README.rst b/modules/stock_supply/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/stock_supply/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/stock_supply/__init__.py b/modules/stock_supply/__init__.py
new file mode 100644
index 0000000000..4d97a706ba
--- /dev/null
+++ b/modules/stock_supply/__init__.py
@@ -0,0 +1,30 @@
+# 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 order_point
+from . import ir
+from . import product
+from . import purchase_request
+from . import shipment
+from . import location
+from . import stock
+
+
+def register():
+ Pool.register(
+ order_point.OrderPoint,
+ product.Product,
+ product.ProductSupplier,
+ purchase_request.PurchaseConfiguration,
+ purchase_request.PurchaseConfigurationSupplyPeriod,
+ purchase_request.PurchaseRequest,
+ shipment.ShipmentInternal,
+ location.Location,
+ location.LocationLeadTime,
+ stock.SupplyStart,
+ ir.Cron,
+ module='stock_supply', type_='model')
+ Pool.register(
+ stock.Supply,
+ module='stock_supply', type_='wizard')
diff --git a/modules/stock_supply/doc/index.rst b/modules/stock_supply/doc/index.rst
new file mode 100644
index 0000000000..4242b81b73
--- /dev/null
+++ b/modules/stock_supply/doc/index.rst
@@ -0,0 +1,47 @@
+Stock Supply Module
+###################
+
+The Stock Supply module add automatic supply mechanisms and introduce
+the concepts of order point.
+
+Order Point
+***********
+
+An order point define minimum, maximum and target quantities for a product on a
+location.
+
+* The minimum quantity is the threshold quantity below which the provisioning
+ process will be triggered.
+
+* The maximum quantity is the threshold quantity above which the overflowing
+ process will be triggered.
+
+* The target quantity is the quantity that will be found in the location after
+ the provisioning / overflowing process has been completed.
+
+An order point also define a type which can be:
+
+* Internal
+
+ An Internal order point is defined on a Storage location, it also defines a
+ provisioning and/or an overflowing location. If the minimum quantity is
+ reached it will result in the creation of an internal shipment between the
+ provisioning location and the Storage location. If the maximum quantity is
+ reached it will result in the creation of an internal shipment between the
+ storage location and the overflowing location.
+
+* Purchase
+
+ A Purchase order point is defined on a warehouse location. If the
+ minimal quantity is reached on the warehouse it will result in a
+ purchase request.
+
+The internal shipments and purchase requests are created by the supply wizard
+with respect to stock levels and existing shipments and requests. The
+stock levels are computed between the next two supply dates computed over the
+Supply Period from the configuration (default: 1 day). If the stock level of a
+product without order point on the given warehouse is below zero, a purchase
+request is also created. The same happens if the stock level of a storage
+location with a provisioning location is below zero. Likewise, if the stock
+level of a storage is above zero and an overflow location is defined on the
+location then an internal shipment will be created.
diff --git a/modules/stock_supply/exceptions.py b/modules/stock_supply/exceptions.py
new file mode 100644
index 0000000000..887d396e96
--- /dev/null
+++ b/modules/stock_supply/exceptions.py
@@ -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.exceptions import UserWarning
+from trytond.model.exceptions import ValidationError
+
+
+class SupplyWarning(UserWarning):
+ pass
+
+
+class OrderPointValidationError(ValidationError):
+ pass
diff --git a/modules/stock_supply/ir.py b/modules/stock_supply/ir.py
new file mode 100644
index 0000000000..27960e0359
--- /dev/null
+++ b/modules/stock_supply/ir.py
@@ -0,0 +1,14 @@
+# The COPYRIGHT file at the top level of this repository contains the full
+# copyright notices and license terms.
+from trytond.pool import PoolMeta
+
+
+class Cron(metaclass=PoolMeta):
+ __name__ = 'ir.cron'
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.method.selection.extend([
+ ('stock.order_point|supply_stock', "Supply Stock"),
+ ])
diff --git a/modules/stock_supply/locale/bg.po b/modules/stock_supply/locale/bg.po
new file mode 100644
index 0000000000..f5406ea066
--- /dev/null
+++ b/modules/stock_supply/locale/bg.po
@@ -0,0 +1,209 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Пренареждания"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Местонахождение на провизии"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Фирма"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Местоположение"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Максимално количество"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Минимално количество"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Продукт"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Местонахождение на провизии"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Местонахождение на съхранение"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Вид"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Единица"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Десетични единици"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Местонахождение на склад"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Пренареждания"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Пренареждания"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Вътрешно"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Управление на покупки"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Разрешено е само едно пренареждане за всяка двойка продукт-местонахождение."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Пренареждане"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Вътрешно"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Покупка"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Вид пренареждане"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Информация за продукт"
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Създаване"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Отказ"
diff --git a/modules/stock_supply/locale/ca.po b/modules/stock_supply/locale/ca.po
new file mode 100644
index 0000000000..bc0466323c
--- /dev/null
+++ b/modules/stock_supply/locale/ca.po
@@ -0,0 +1,202 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Regles de proveïment"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Període de subministre"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Període de subministre"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Ubicació de excessos"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Ubicació de l'aprovisionament"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Ubicació"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Quantitat màxima"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Quantitat mínima"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Ubicació de excessos"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Producte"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Ubicació de l'aprovisionament"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Ubicació interna"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Quantitat desitjada"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Tipus"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Unitat"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Dígits de la unitat"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Magatzem"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "Deixeu-lo buit per no tenir una ubicació d'excessos per defecte."
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr "Deixeu-lo buit per no tenir una ubicació de proveïment per defecte."
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Regles de proveïment"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Regles de proveïment"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Regles de proveïment"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Aprovisiona existències"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Tot"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Intern"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Compra"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "Existeixen moviments de client endarrerits."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "Existeixen moviments de proveïdor endarrerits."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"No es pot definir dos regles d'aprovisionament amb ubicacions oposades(\n"
+"de la \"ubicació d'emmagatzemament\" a la \"ubicació d'aprovisionament\" i viceversa)."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"No es pot definir dos regles d'aprovisionament amb ubicacions oposades\n"
+"(de la \"ubicació d'emmagatzemament\" a la \"ubicació d'aprovisionament\" i viceversa)."
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Només es permet una regla de proveïment per cada combinació de producte-"
+"ubicació."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Usuari a les empreses"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Regles de proveïment"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Aprovisiona existències"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Configuració del període d'aprovisionament en compres"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Regla de proveïment"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Aprovisionament d'existències"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Aprovisiona existències"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Intern"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Compra"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Tipus de regla de proveïment"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Informació del producte"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Aprovisionar existències?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Crea"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Cancel·la"
diff --git a/modules/stock_supply/locale/cs.po b/modules/stock_supply/locale/cs.po
new file mode 100644
index 0000000000..8c8025712c
--- /dev/null
+++ b/modules/stock_supply/locale/cs.po
@@ -0,0 +1,203 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr ""
diff --git a/modules/stock_supply/locale/de.po b/modules/stock_supply/locale/de.po
new file mode 100644
index 0000000000..eba54391ed
--- /dev/null
+++ b/modules/stock_supply/locale/de.po
@@ -0,0 +1,204 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Bestellpunkte"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Beschaffungszeitraum"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Beschaffungszeitraum"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Lager für Überschüsse"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Bereitstellungsort"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Unternehmen"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Lagerort"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Maximale Menge"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Minimale Menge"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Lager für Überschüsse"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Artikel"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Bereitstellungsort"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Lagerort"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Zielmenge"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Typ"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Einheit"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Nachkommastellen"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Warenlager"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+"Leer lassen, wenn kein Lagerort für Überschüsse verwendet werden soll."
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+"Leer lassen, wenn kein Standardbereitstellungsort verwendet werden soll."
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Bestellpunkte"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Bestellpunkte"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Bestellpunkte"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Waren- und Materialbedarf ermitteln"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Alle"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Intern"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Einkauf"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "Einige Lagerbewegungen zu Kunden sind verspätet."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "Einige Lagerbewegungen von Lieferanten sind verspätet."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Zwei Bestellpunkte eines gleichen Artikels mit sich widersprechenden Lagerorten können nicht erstellt werden\n"
+"(von \"Lagerort für Überschüsse\" zu \"Bereitstellungsort\" und umgekehrt)."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Zwei Bestellpunkte eines gleichen Artikels mit entgegengesetzten Lagerorten können nicht erstellt werden\n"
+"(von \"Lagerzone\" zu \"Bereitstellungsort\" und umgekehrt)."
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Es ist nur ein Bestellpunkt für jede Paarung aus Artikel und Lagerort "
+"erlaubt."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Benutzer im Unternehmen"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Bestellpunkte"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Waren- und Materialbedarf ermitteln"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Einstellungen Einkauf Beschaffungszeitraum"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Bestellpunkt"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Waren- und Materialbedarf ermitteln"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Waren- und Materialbedarf ermitteln"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Intern"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Einkauf"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Typ Bestellpunkt"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Info"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Waren- und Materialbedarf ermitteln?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Erstellen"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Abbrechen"
diff --git a/modules/stock_supply/locale/es.po b/modules/stock_supply/locale/es.po
new file mode 100644
index 0000000000..9d7158025e
--- /dev/null
+++ b/modules/stock_supply/locale/es.po
@@ -0,0 +1,203 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Reglas de abastecimiento"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Período de suministro"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Período de suministro"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Ubicación de excesos"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Ubicación del abastecimiento"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Ubicación"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Cantidad máxima"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Cantidad mínima"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Ubicación de excesos"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Producto"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Ubicación del abastecimiento"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Ubicación interna"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Cantidad destino"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Tipo"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Unidad"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Dígitos de la unidad"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Ubicación del almacén"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "Dejarlo vacío para no tener una ubicación de excesos por defecto."
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+"Dejarlo vacío para no tener una ubicación de abastecimiento por defecto."
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Reglas de abastecimiento"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Reglas de abastecimiento"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Reglas de abastecimiento"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Aprovisionar existencias"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Todo"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Interna"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Compra"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "Existen movimientos de cliente retrasados."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "Existen movimientos de proveedor retrasados."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"No se puede definir dos reglas de aprovisionamiento con ubicaciones opuestas\n"
+"(de la \"ubicación de almacenamiento\" a la \"ubicación del abastecimiento\" y viceversa)."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"No se puede definir dos reglas de aprovisionamiento con ubicaciones opuestas\n"
+"(de la \"ubicación de almacenamiento\" a la \"ubicación del abastecimiento\" y viceversa)."
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Sólo se permite una regla de abastecimiento por cada combinación de "
+"producto-ubicación."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Usuario en las empresas"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Reglas de abastecimiento"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Aprovisionar existencias"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Configuración del periodo de aprovisionamiento en compras"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Regla de abastecimiento"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Aprovisionamiento de existencias"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Aprovisionar existencias"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Interno"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Compra"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Tipo de regla de abastecimiento"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Información del producto"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Aprovisionar existencias?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Crear"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
diff --git a/modules/stock_supply/locale/es_419.po b/modules/stock_supply/locale/es_419.po
new file mode 100644
index 0000000000..02490ba6f3
--- /dev/null
+++ b/modules/stock_supply/locale/es_419.po
@@ -0,0 +1,197 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Ubicación del almacenamiento"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Ubicación de la bodega"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr ""
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Interna"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr ""
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Interna"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr ""
diff --git a/modules/stock_supply/locale/et.po b/modules/stock_supply/locale/et.po
new file mode 100644
index 0000000000..55bfb98f0a
--- /dev/null
+++ b/modules/stock_supply/locale/et.po
@@ -0,0 +1,198 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Tellimiskohad"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Tarneaeg"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Tarneaeg"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Ettevõte"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Asukoht"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Maksimaalne kogus"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Minimaalne kogus"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Toode"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Lao asukoht"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Tüüp"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Ühik"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Ühiku numbriväärtus"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Lao asukoht"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Tellimiskohad"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Tellimiskohad"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Tellimiskohad"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "hanke ladu"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Kõik"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Sisemine"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Ost"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Kasutaja ettevõttes"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Tellimiskohad"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Hanke ladu"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Hanke seadistus - hanke periood"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Tellimiskohad"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Hanke ladu"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "hanke ladu"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Sisemine"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Ost"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Tellimiskoha tüüp"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Toote info"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Hanke ladu?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Loo"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Tühista"
diff --git a/modules/stock_supply/locale/fa.po b/modules/stock_supply/locale/fa.po
new file mode 100644
index 0000000000..eadc880f56
--- /dev/null
+++ b/modules/stock_supply/locale/fa.po
@@ -0,0 +1,203 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "نقاط سفارش"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "دوره عرضه"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "دوره عرضه"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "مکان لبریز شده"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "مکان تأمیین کننده"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "شرکت"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "مکان"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "مقدار حداکثر"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "حداقل مقدار"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "مکان لبریز شده"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "محصول"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "مکان تأمیین کننده"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "محل انبار"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "مقدار هدف"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "نوع"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "واحد"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "ارقام واحد"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "مکان انبار"
+
+#, fuzzy
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "برای بدون لبریز پیش فرض، خالی بگذارید"
+
+#, fuzzy
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr "برای تأمین کننده غیرپیش فرض خالی بگذارید"
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "نقاط سفارش"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "نقاط سفارش"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "نقاط سفارش"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "عرضه موجودی"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "همه"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "داخلی"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "خرید"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "برخی از جابجایی مشتری تأخیر دارند."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "برخی از جابجایی تأمین کننده تأخیر دارند."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"شما نمیتوانید برای هر محصول دو نقطه سفارش با مکان های متفاوت تعریف کنید\n"
+"(از محل سرریز به محل تأمین و برعکس)."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"شما نمیتوانید برای هر محصول دو نقطه سفارش با مکان های متفاوت تعریف کنید\n"
+"(از محل ذخیره کالا به محل تأمین و برعکس)."
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr "برای هر جفت مکان - محصول فقط یک نقطه سفارش مجاز است."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "نقاط سفارش"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "عرضه موجودی"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "پیکربندی خرید ؛ دوره تامین"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "نقطه سفارش"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "عرضه موجودی"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "عرضه موجودی"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "داخلی"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "خرید"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "نوع نقطه سفارش"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "اطلاعات محصول"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "عرضه موجودی؟"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "ایجاد"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "انصراف"
diff --git a/modules/stock_supply/locale/fi.po b/modules/stock_supply/locale/fi.po
new file mode 100644
index 0000000000..8c8025712c
--- /dev/null
+++ b/modules/stock_supply/locale/fi.po
@@ -0,0 +1,203 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr ""
diff --git a/modules/stock_supply/locale/fr.po b/modules/stock_supply/locale/fr.po
new file mode 100644
index 0000000000..30bc7f8bb6
--- /dev/null
+++ b/modules/stock_supply/locale/fr.po
@@ -0,0 +1,202 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Règles d'approvisionnement"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Période d'approvisionnement"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Période d'approvisionnement"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Emplacement de surplus"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Emplacement d’approvisionnement"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Société"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Emplacement"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Quantité maximale"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Quantité minimale"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Emplacement de surplus"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Produit"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Emplacement d'approvisionnement"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Emplacement de stockage"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Quantité souhaitée"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Type"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Unité"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Décimales de l'unité"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Entrepôt"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "Laisser vide pour aucun surplus par défaut."
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr "Laisser vide pour aucun approvisionnement par défaut."
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Règles d'approvisionnement"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Règles d'approvisionnement"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Règles d'approvisionnement"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Approvisionner le stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Toutes"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internes"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Achats"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "Il y a des mouvements clients qui sont en retard."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "Il y a des mouvements fournisseurs qui sont en retard."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Vous ne pouvez pas définir deux règles d'approvisionnement pour le même produit avec des emplacements opposés\n"
+"(d' « Emplacement de stockage » à « Emplacement d'approvisionnement » et vice versa)."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Vous ne pouvez pas définir deux règles d'approvisionnement pour le même produit avec des emplacements opposés\n"
+"(d' « Emplacement de stockage » à « Emplacement d'approvisionnement » et vice versa)."
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Une seule règle d'approvisionnement est autorisée par paire produit-"
+"emplacement."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Utilisateur dans les sociétés"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Règles d'approvisionnement"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Approvisionner le stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Configuration d'achat Période d'approvisionnement"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Règle d'approvisionnement"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Approvisionner le stock"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Approvisionner le stock"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Interne"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Achat"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Type de Règle d'approvisionnement"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Info produit"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Approvisionner le stock ?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Créer"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Annuler"
diff --git a/modules/stock_supply/locale/hu.po b/modules/stock_supply/locale/hu.po
new file mode 100644
index 0000000000..43f3553b6c
--- /dev/null
+++ b/modules/stock_supply/locale/hu.po
@@ -0,0 +1,202 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Újrarendelési pontok"
+
+#, fuzzy
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Beszerzési időszak"
+
+#, fuzzy
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Beszerzési időszak"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Felesleg tárhelye"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Beszerzési tárhely"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Cég"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Tárhely"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Maximális készletszint"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Minimális készletszint"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Felesleg tárhelye"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Termék"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Beszerzési tárhely"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Cél tárhely"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Cél készletszint"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Áruellátás forrása"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Mértékegység"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Tizedes vessző utáni számjegy"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Raktár"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+"Hagyja üresen, ha nem szeretné a tárhelyen lévő készletet automatikusan "
+"átvezetni a felesleg tárhelyre."
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+"Hagyja üresen, ha nem szeretne a tárhelyre érkező rendelésekhez "
+"automatikusan készletet a beszerzési tárhelyről áthozni."
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Újrarendelési pontok"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Újrarendelési pontok"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Újrarendelési pontok"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Áruellátás futtatása"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "összes"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "belső"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "beszállítótól"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "Némelyik vevőkhöz induló készletmozgás már késésben van."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "Némelyik beszállítótól érkező készletmozgás már késésben van."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr "Egy termék/tárhely kombinációnak csak egy újrarendelési pontja lehet."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Újrarendelési pontok"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Áruellátás futtatása"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Újrarendelési pont"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Áruellátás futtatása"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Áruellátás futtatása"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "belső"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "beszállítótól"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Áruellátási beállítások"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Termék és mennyiség"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Létrehozza a beszerzési igényeket/belső szállítmányokat?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Létrehozás"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Mégse"
diff --git a/modules/stock_supply/locale/id.po b/modules/stock_supply/locale/id.po
new file mode 100644
index 0000000000..57dcd2c171
--- /dev/null
+++ b/modules/stock_supply/locale/id.po
@@ -0,0 +1,197 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Perusahaan"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Lokasi"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Produk"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Lokasi Penyimpanan"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Pembelian"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Pengguna di dalam perusahaan"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr ""
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Pembelian"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Buat"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Batal"
diff --git a/modules/stock_supply/locale/it.po b/modules/stock_supply/locale/it.po
new file mode 100644
index 0000000000..0fabb4dc63
--- /dev/null
+++ b/modules/stock_supply/locale/it.po
@@ -0,0 +1,210 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Azienda"
+
+#, fuzzy
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Luogo"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Prodotto"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Tipo"
+
+#, fuzzy
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Unità"
+
+#, fuzzy
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Posizioni Unità"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Tutti"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Acquisto"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Acquisto"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Creazione"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Annulla"
diff --git a/modules/stock_supply/locale/lo.po b/modules/stock_supply/locale/lo.po
new file mode 100644
index 0000000000..6033185f32
--- /dev/null
+++ b/modules/stock_supply/locale/lo.po
@@ -0,0 +1,213 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "ຫ້ອງການ/ສຳນັກງານ"
+
+#, fuzzy
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "ບ່ອນຢູ່"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "ຜະລິດຕະພັນ"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "ຮູບແບບ"
+
+#, fuzzy
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "ໜ່ວຍ"
+
+#, fuzzy
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "ຫົວໜ່ວຍເສດ"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "ທັງໝົດ"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "ສັ່ງຊື້"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "ສັ່ງຊື້"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "ສ້າງ"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "ຍົກເລີກ"
diff --git a/modules/stock_supply/locale/lt.po b/modules/stock_supply/locale/lt.po
new file mode 100644
index 0000000000..b9d438204e
--- /dev/null
+++ b/modules/stock_supply/locale/lt.po
@@ -0,0 +1,203 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Organizacija"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr ""
diff --git a/modules/stock_supply/locale/nl.po b/modules/stock_supply/locale/nl.po
new file mode 100644
index 0000000000..c17a4ea35f
--- /dev/null
+++ b/modules/stock_supply/locale/nl.po
@@ -0,0 +1,201 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Bestel regels"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Leveringstermijn"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Leveringstermijn"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "locatie overschot (surplus)"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "provisioning Locatie"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Bedrijf"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Plaats"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Maximale hoeveelheid"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Minimale hoeveelheid"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "locatie overschot (surplus)"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Product"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "provisioning Locatie"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Opslaglocatie"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Gewenste hoeveelheid"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Type"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Eenheid"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Decimalen eenheid"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Magazijnlocatie"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "Laat leeg als er geen opslaglocatie wordt gebruikt voor overschotten."
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr "Laat leeg als er geen opslaglocatie wordt gebruikt voor overschotten."
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Bestel punten"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Bestel punten"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Bestel punten"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "voorraad levering"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Alle"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Intern"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Aankoop"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr "Er zijn enkele klantbewegingen die te laat zijn."
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr "Er zijn enkele leveranciersbewegingen die te laat zijn."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"U kunt voor hetzelfde product niet twee bestelpunten met tegenovergestelde locaties definiëren\n"
+"(van \"Overstock locatie\" naar \"provisielocatie\" en vice versa)."
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"U kunt voor hetzelfde product niet twee bestelpunten met tegenovergestelde locaties definiëren\n"
+"(van \"Opslag locatie\" naar \"provisielocatie\" en vice versa)."
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr "Per product-locatiepaar is slechts één bestelpunt toegestaan."
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr "Gebruiker in het bedrijf"
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Bestel punten"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "voorraad levering"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "configuratie aankoop verwervingsperiode"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Bestelpunt"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "voorraad levering"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "voorraad levering"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Intern"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Aankoop"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Bestelpunttype"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Product informatie"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Voorraad inslaan?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Maken"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Annuleer"
diff --git a/modules/stock_supply/locale/pl.po b/modules/stock_supply/locale/pl.po
new file mode 100644
index 0000000000..474a27005f
--- /dev/null
+++ b/modules/stock_supply/locale/pl.po
@@ -0,0 +1,208 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Firma"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Lokalizacja"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Maksymalna ilość"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Minimalna ilość"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Produkt"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Docelowa ilość"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Typ"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Jednostka"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Cyfry jednostki"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Lokalizacja magazynu"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Zakup"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Informacja o produkcie"
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Utwórz"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Anuluj"
diff --git a/modules/stock_supply/locale/pt.po b/modules/stock_supply/locale/pt.po
new file mode 100644
index 0000000000..c4a979a5b4
--- /dev/null
+++ b/modules/stock_supply/locale/pt.po
@@ -0,0 +1,219 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Regras de Abastecimento"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Período de Abastecimento"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Período de Abastecimento"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Localização de Transbordo"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Localização do Abastecimento"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Localização"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Quantidade máxima"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Quantidade mínima"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Localização de Transbordo"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Produto"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Localização do Abastecimento"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Local de armazenamento"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Quantidade Alvo"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Tipo"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Unidade"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Dígitos da unidade"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Localização do Almoxarifado"
+
+#, fuzzy
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "Deixe vazio para nenhum transbordo padrão"
+
+#, fuzzy
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr "Deixe vazio para nenhum abastecimento padrão"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+#, fuzzy
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Você não pode definir duas regras de abastecimento no mesmo produto com "
+"localizações opostas (de \"Local de Armazenamento\" para \"Local de "
+"Transbordo\" e vice versa)."
+
+#, fuzzy
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Você não pode definir duas regras de abastecimento no mesmo produto com "
+"localizações opostas (de \"Local de Armazenamento\" para \"Local de "
+"Transbordo\" e vice versa)."
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Apenas uma regras de abastecimento é permitida para cada par produto-"
+"localização."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Configurações de Compra Período de Abastecimento"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Regra de Abastecimento"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Abastecer Estoque"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Interno"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Compra"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Tipo de Regra de Abastecimento"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Informação do Produto"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Abastecer Estoque?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Criar"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
diff --git a/modules/stock_supply/locale/ro.po b/modules/stock_supply/locale/ro.po
new file mode 100644
index 0000000000..bc7a38f8a6
--- /dev/null
+++ b/modules/stock_supply/locale/ro.po
@@ -0,0 +1,196 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Companie"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Produs"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr ""
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr ""
diff --git a/modules/stock_supply/locale/ru.po b/modules/stock_supply/locale/ru.po
new file mode 100644
index 0000000000..c410dcd7c0
--- /dev/null
+++ b/modules/stock_supply/locale/ru.po
@@ -0,0 +1,210 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Точки заказа"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Местоположение резервирования"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Организация"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Местоположение"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Максимальное кол-во"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Минимальное кол-во"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Продукт"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Местоположение резервирования"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Хранение Местоположение"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Тип"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "Единица измерения"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Кол-во цифр после запятой"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Местополжение на складе"
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Точки заказа"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Точки заказа"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "Все"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Внутренний"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Покупки"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+"Возможна только одна точка заказа для каждой пары продукт-местоположение."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Точка заказа"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Внутренний"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Покупки"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Типы точек заказа"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Информация о продукте"
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Создать"
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Отменить"
diff --git a/modules/stock_supply/locale/sl.po b/modules/stock_supply/locale/sl.po
new file mode 100644
index 0000000000..da63c37486
--- /dev/null
+++ b/modules/stock_supply/locale/sl.po
@@ -0,0 +1,215 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Točke naročanja"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Obdobje dobave"
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Obdobje dobave"
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Prelivna lokacija"
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Preskrbovalna lokacija"
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr "Družba"
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr "Lokacija"
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr "Največja količina"
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr "Najmanjša količina"
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr "Prelivna lokacija"
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr "Izdelek"
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr "Preskrbovalna lokacija"
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr "Shramba"
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr "Ciljna količina"
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "Tip"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr "enota"
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr "Decimalke"
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr "Skladišče"
+
+#, fuzzy
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr "Pusti prazno, če ni privzetega preliva"
+
+#, fuzzy
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr "Pusti prazno, če ni privzete preskrbe"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+#, fuzzy
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Dveh točk naročanja z nasprotnima lokacijama (iz \"Shrambe\" na "
+"\"Preskrbovalno lokacijo\" in obratno) ni možno določiti na istem izdelku."
+
+#, fuzzy
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+"Dveh točk naročanja z nasprotnima lokacijama (iz \"Shrambe\" na "
+"\"Preskrbovalno lokacijo\" in obratno) ni možno določiti na istem izdelku."
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr "Dovoljena je samo ena točka naročanja za vsak par izdelek - lokacija."
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr "Konfiguracija obdobja nabavne dobave"
+
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Točka naročanja"
+
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Oskrba zaloge"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Interno"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Nabavni nalog"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr "Tip točke naročanja"
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr "Podatek o izdelku"
+
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Oskrba zaloge?"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr "Izdelava"
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "Prekliči"
diff --git a/modules/stock_supply/locale/tr.po b/modules/stock_supply/locale/tr.po
new file mode 100644
index 0000000000..8c8025712c
--- /dev/null
+++ b/modules/stock_supply/locale/tr.po
@@ -0,0 +1,203 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "All"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr ""
diff --git a/modules/stock_supply/locale/zh_CN.po b/modules/stock_supply/locale/zh_CN.po
new file mode 100644
index 0000000000..475146c245
--- /dev/null
+++ b/modules/stock_supply/locale/zh_CN.po
@@ -0,0 +1,206 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:product.product,order_points:"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "field:purchase.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:purchase.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:stock.location,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.location,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:stock.order_point,location:"
+msgid "Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,max_quantity:"
+msgid "Maximal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,min_quantity:"
+msgid "Minimal Quantity"
+msgstr ""
+
+msgctxt "field:stock.order_point,overflowing_location:"
+msgid "Overflowing Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:stock.order_point,provisioning_location:"
+msgid "Provisioning Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,storage_location:"
+msgid "Storage Location"
+msgstr ""
+
+msgctxt "field:stock.order_point,target_quantity:"
+msgid "Target Quantity"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:stock.order_point,type:"
+msgid "Type"
+msgstr "类型"
+
+msgctxt "field:stock.order_point,unit:"
+msgid "Unit"
+msgstr ""
+
+msgctxt "field:stock.order_point,unit_digits:"
+msgid "Unit Digits"
+msgstr ""
+
+msgctxt "field:stock.order_point,warehouse_location:"
+msgid "Warehouse Location"
+msgstr ""
+
+msgctxt "help:stock.location,overflowing_location:"
+msgid "Leave empty for no default overflowing."
+msgstr ""
+
+msgctxt "help:stock.location,provisioning_location:"
+msgid "Leave empty for no default provisioning."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_order_point_form_relate2"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.action,name:act_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_all"
+msgid "All"
+msgstr "全部"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_internal"
+msgid "Internal"
+msgstr "Internal"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_purchase"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "model:ir.message,text:msg_late_customer_moves"
+msgid "There are some customer moves that are late."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_supplier_moves"
+msgid "There are some supplier moves that are late."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_overflowing_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Overflowing Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt ""
+"model:ir.message,text:msg_order_point_concurrent_provisioning_location_internal"
+msgid ""
+"You cannot define for the same product two order points with opposite locations\n"
+"(from \"Storage Location\" to \"Provisioning Location\" and vice versa)."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_order_point_unique"
+msgid "Only one order point is allowed for each product-location pair."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_order_point_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_order_point_form"
+msgid "Order Points"
+msgstr "Order Points"
+
+msgctxt "model:ir.ui.menu,name:menu_stock_supply"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+msgctxt "model:purchase.configuration.supply_period,name:"
+msgid "Purchase Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:stock.order_point,name:"
+msgid "Order Point"
+msgstr "Order Points"
+
+#, fuzzy
+msgctxt "model:stock.supply.start,name:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Supply Stock"
+msgstr "Supply Stock"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Internal"
+msgstr "Internal"
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Purchase"
+msgstr "Purchase"
+
+msgctxt "view:stock.order_point:"
+msgid "Order Point Type"
+msgstr ""
+
+msgctxt "view:stock.order_point:"
+msgid "Product Info"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:stock.supply.start:"
+msgid "Supply Stock?"
+msgstr "Supply Stock"
+
+msgctxt "wizard_button:stock.supply,start,create_:"
+msgid "Create"
+msgstr ""
+
+#, fuzzy
+msgctxt "wizard_button:stock.supply,start,end:"
+msgid "Cancel"
+msgstr "取消"
diff --git a/modules/stock_supply/location.py b/modules/stock_supply/location.py
new file mode 100644
index 0000000000..9499396cc5
--- /dev/null
+++ b/modules/stock_supply/location.py
@@ -0,0 +1,66 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+
+from trytond.pool import PoolMeta, Pool
+from trytond.model import fields
+from trytond.pyson import Eval
+
+
+class Location(metaclass=PoolMeta):
+ __name__ = 'stock.location'
+
+ provisioning_location = fields.Many2One('stock.location',
+ 'Provisioning Location',
+ states={
+ 'invisible': Eval('type') != 'storage',
+ },
+ domain=[
+ ('type', 'in', ['storage', 'view']),
+ ],
+ depends=['type'],
+ help='Leave empty for no default provisioning.')
+ overflowing_location = fields.Many2One('stock.location',
+ 'Overflowing Location',
+ states={
+ 'invisible': Eval('type') != 'storage',
+ },
+ domain=[
+ ('type', 'in', ['storage', 'view']),
+ ],
+ depends=['type'],
+ help='Leave empty for no default overflowing.')
+
+
+class LocationLeadTime(metaclass=PoolMeta):
+ __name__ = 'stock.location.lead_time'
+
+ @classmethod
+ def get_max_lead_time(cls):
+ """Return the biggest lead time
+ increased by the maximum extra lead times"""
+ lead_time = datetime.timedelta(0)
+ lead_times = cls.search([])
+ if lead_times:
+ lead_time = sum(
+ (r.lead_time for r in lead_times if r.lead_time),
+ datetime.timedelta(0))
+ extra_lead_times = cls._get_extra_lead_times()
+ if extra_lead_times:
+ lead_time += max(extra_lead_times)
+ return lead_time
+
+ @classmethod
+ def _get_extra_lead_times(cls):
+ 'Return a list of extra lead time'
+ pool = Pool()
+ ProductSupplier = pool.get('purchase.product_supplier')
+ extra = []
+
+ product_suppliers = ProductSupplier.search(
+ [('lead_time', '!=', None)],
+ order=[('lead_time', 'DESC')], limit=1)
+ if product_suppliers:
+ product_supplier, = product_suppliers
+ extra.append(product_supplier.lead_time)
+ return extra
diff --git a/modules/stock_supply/location.xml b/modules/stock_supply/location.xml
new file mode 100644
index 0000000000..82f4599b8c
--- /dev/null
+++ b/modules/stock_supply/location.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ stock.location
+
+ location_form
+
+
+
diff --git a/modules/stock_supply/message.xml b/modules/stock_supply/message.xml
new file mode 100644
index 0000000000..dc06b0313f
--- /dev/null
+++ b/modules/stock_supply/message.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Only one order point is allowed for each product-location pair.
+
+
+ You cannot define for the same product two order points with opposite locations
+(from "Storage Location" to "Provisioning Location" and vice versa).
+
+
+ You cannot define for the same product two order points with opposite locations
+(from "Overflowing Location" to "Provisioning Location" and vice versa).
+
+
+ There are some supplier moves that are late.
+
+
+ There are some customer moves that are late.
+
+
+
diff --git a/modules/stock_supply/order_point.py b/modules/stock_supply/order_point.py
new file mode 100644
index 0000000000..6117b742f4
--- /dev/null
+++ b/modules/stock_supply/order_point.py
@@ -0,0 +1,263 @@
+# 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 sql import Null
+
+from trytond.i18n import gettext
+from trytond.model import ModelView, ModelSQL, fields
+from trytond.pool import Pool
+from trytond.pyson import If, Equal, Eval, Not, In
+from trytond.transaction import Transaction
+
+from .exceptions import OrderPointValidationError
+
+
+class OrderPoint(ModelSQL, ModelView):
+ """
+ Order Point
+ Provide a way to define a supply policy for each
+ product on each locations. Order points on warehouse are
+ considered by the supply scheduler to generate purchase requests.
+ """
+ __name__ = 'stock.order_point'
+ product = fields.Many2One('product.product', 'Product', required=True,
+ select=True,
+ domain=[
+ ('type', '=', 'goods'),
+ ('consumable', '=', False),
+ ('purchasable', 'in', If(Equal(Eval('type'), 'purchase'),
+ [True], [True, False])),
+ ],
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['type', 'company'])
+ warehouse_location = fields.Many2One('stock.location',
+ 'Warehouse Location', select=True,
+ domain=[('type', '=', 'warehouse')],
+ states={
+ 'invisible': Not(Equal(Eval('type'), 'purchase')),
+ 'required': Equal(Eval('type'), 'purchase'),
+ },
+ depends=['type'])
+ storage_location = fields.Many2One('stock.location', 'Storage Location',
+ select=True,
+ domain=[('type', '=', 'storage')],
+ states={
+ 'invisible': Not(Equal(Eval('type'), 'internal')),
+ 'required': Equal(Eval('type'), 'internal'),
+ },
+ depends=['type'])
+ location = fields.Function(fields.Many2One('stock.location', 'Location'),
+ 'get_location', searcher='search_location')
+ provisioning_location = fields.Many2One(
+ 'stock.location', 'Provisioning Location',
+ domain=[('type', 'in', ['storage', 'view'])],
+ states={
+ 'invisible': Not(Equal(Eval('type'), 'internal')),
+ 'required': ((Eval('type') == 'internal')
+ & (Eval('min_quantity', None) != None)), # noqa: E711
+ },
+ depends=['type', 'min_quantity'])
+ overflowing_location = fields.Many2One(
+ 'stock.location', 'Overflowing Location',
+ domain=[('type', 'in', ['storage', 'view'])],
+ states={
+ 'invisible': Eval('type') != 'internal',
+ 'required': ((Eval('type') == 'internal')
+ & (Eval('max_quantity', None) != None)), # noqa: E711
+ },
+ depends=['type', 'max_quantity'])
+ type = fields.Selection(
+ [('internal', 'Internal'),
+ ('purchase', 'Purchase')],
+ 'Type', select=True, required=True)
+ min_quantity = fields.Float('Minimal Quantity',
+ digits=(16, Eval('unit_digits', 2)),
+ states={
+ # required for purchase and production types
+ 'required': Eval('type') != 'internal',
+ },
+ domain=['OR',
+ ('min_quantity', '=', None),
+ ('min_quantity', '<=', Eval('target_quantity', 0)),
+ ],
+ depends=['unit_digits', 'target_quantity', 'type'])
+ target_quantity = fields.Float('Target Quantity', required=True,
+ digits=(16, Eval('unit_digits', 2)),
+ domain=[
+ ['OR',
+ ('min_quantity', '=', None),
+ ('target_quantity', '>=', Eval('min_quantity', 0)),
+ ],
+ ['OR',
+ ('max_quantity', '=', None),
+ ('target_quantity', '<=', Eval('max_quantity', 0)),
+ ],
+ ],
+ depends=['unit_digits', 'min_quantity', 'max_quantity'])
+ max_quantity = fields.Float('Maximal Quantity',
+ digits=(16, Eval('unit_digits', 2)),
+ states={
+ 'invisible': Eval('type') != 'internal',
+ },
+ domain=['OR',
+ ('max_quantity', '=', None),
+ ('max_quantity', '>=', Eval('target_quantity', 0)),
+ ],
+ depends=['unit_digits', 'type', 'target_quantity'])
+ company = fields.Many2One('company.company', 'Company', required=True,
+ domain=[
+ ('id', If(In('company', Eval('context', {})), '=', '!='),
+ Eval('context', {}).get('company', -1)),
+ ])
+ unit = fields.Function(fields.Many2One('product.uom', 'Unit'), 'get_unit')
+ unit_digits = fields.Function(fields.Integer('Unit Digits'),
+ 'get_unit_digits')
+
+ @classmethod
+ def __register__(cls, module_name):
+ cursor = Transaction().connection.cursor()
+ sql_table = cls.__table__()
+ table = cls.__table_handler__(module_name)
+
+ # Migration from 4.2
+ table.drop_constraint('check_max_qty_greater_min_qty')
+ table.not_null_action('min_quantity', 'remove')
+ table.not_null_action('max_quantity', 'remove')
+ target_qty_exist = table.column_exist('target_quantity')
+
+ super(OrderPoint, cls).__register__(module_name)
+
+ # Migration from 4.2
+ if not target_qty_exist:
+ cursor.execute(*sql_table.update(
+ [sql_table.target_quantity, sql_table.max_quantity],
+ [sql_table.max_quantity, Null]))
+
+ @staticmethod
+ def default_type():
+ return "purchase"
+
+ @fields.depends('product', '_parent_product.default_uom')
+ def on_change_product(self):
+ self.unit = None
+ self.unit_digits = 2
+ if self.product:
+ self.unit = self.product.default_uom
+ self.unit_digits = self.product.default_uom.digits
+
+ def get_unit(self, name):
+ return self.product.default_uom.id
+
+ def get_unit_digits(self, name):
+ return self.product.default_uom.digits
+
+ @classmethod
+ def validate(cls, orderpoints):
+ super(OrderPoint, cls).validate(orderpoints)
+ cls.check_concurrent_internal(orderpoints)
+ cls.check_uniqueness(orderpoints)
+
+ @classmethod
+ def check_concurrent_internal(cls, orders):
+ """
+ Ensure that there is no 'concurrent' internal order
+ points. I.E. no two order point with opposite location for the
+ same product and same company.
+ """
+ internals = cls.search([
+ ('id', 'in', [o.id for o in orders]),
+ ('type', '=', 'internal'),
+ ])
+ if not internals:
+ return
+
+ for location_name in [
+ 'provisioning_location', 'overflowing_location']:
+ query = []
+ for op in internals:
+ if getattr(op, location_name, None) is None:
+ continue
+ arg = ['AND',
+ ('product', '=', op.product.id),
+ (location_name, '=', op.storage_location.id),
+ ('storage_location', '=',
+ getattr(op, location_name).id),
+ ('company', '=', op.company.id),
+ ('type', '=', 'internal')]
+ query.append(arg)
+ if query and cls.search(['OR'] + query):
+ raise OrderPointValidationError(
+ gettext('stock_supply'
+ '.msg_order_point_concurrent_%s_internal' %
+ location_name))
+
+ @staticmethod
+ def _type2field(type=None):
+ t2f = {
+ 'purchase': 'warehouse_location',
+ 'internal': 'storage_location',
+ }
+ if type is None:
+ return t2f
+ else:
+ return t2f[type]
+
+ @classmethod
+ def check_uniqueness(cls, orders):
+ """
+ Ensure uniqueness of order points. I.E that there is no several
+ order point for the same location, the same product and the
+ same company.
+ """
+ query = ['OR']
+ for op in orders:
+ field = cls._type2field(op.type)
+ arg = ['AND',
+ ('product', '=', op.product.id),
+ (field, '=', getattr(op, field).id),
+ ('id', '!=', op.id),
+ ('company', '=', op.company.id),
+ ]
+ query.append(arg)
+ if cls.search(query):
+ raise OrderPointValidationError(
+ gettext('stock_supply.msg_order_point_unique'))
+
+ def get_rec_name(self, name):
+ return "%s @ %s" % (self.product.name, self.location.name)
+
+ @classmethod
+ def search_rec_name(cls, name, clause):
+ return ['OR',
+ ('location.rec_name',) + tuple(clause[1:]),
+ ('product.rec_name',) + tuple(clause[1:]),
+ ]
+
+ def get_location(self, name):
+ if self.type == 'purchase':
+ return self.warehouse_location.id
+ elif self.type == 'internal':
+ return self.storage_location.id
+
+ @classmethod
+ def search_location(cls, name, domain=None):
+ clauses = ['OR']
+ for type, field in cls._type2field().items():
+ clauses.append([
+ ('type', '=', type),
+ (field,) + tuple(domain[1:]),
+ ])
+ return clauses
+
+ @staticmethod
+ def default_company():
+ return Transaction().context.get('company')
+
+ @classmethod
+ def supply_stock(cls):
+ pool = Pool()
+ StockSupply = pool.get('stock.supply', type='wizard')
+ session_id, _, _ = StockSupply.create()
+ StockSupply.execute(session_id, {}, 'create_')
+ StockSupply.delete(session_id)
diff --git a/modules/stock_supply/order_point.xml b/modules/stock_supply/order_point.xml
new file mode 100644
index 0000000000..56ba26bdd0
--- /dev/null
+++ b/modules/stock_supply/order_point.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+ stock.order_point
+ form
+ order_point_form
+
+
+ stock.order_point
+ tree
+ order_point_tree
+
+
+
+ Order Points
+ stock.order_point
+
+
+
+
+
+
+
+
+
+
+
+
+ Purchase
+
+
+
+
+
+ Internal
+
+
+
+
+
+ All
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ User in companies
+
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply/product.py b/modules/stock_supply/product.py
new file mode 100644
index 0000000000..f9af1b4113
--- /dev/null
+++ b/modules/stock_supply/product.py
@@ -0,0 +1,25 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+
+from trytond.model import fields
+from trytond.pool import PoolMeta, Pool
+
+
+class Product(metaclass=PoolMeta):
+ __name__ = "product.product"
+ order_points = fields.One2Many(
+ 'stock.order_point', 'product', 'Order Points')
+
+
+class ProductSupplier(metaclass=PoolMeta):
+ __name__ = 'purchase.product_supplier'
+
+ def get_supply_period(self):
+ 'Return the supply period for the purchase request'
+ pool = Pool()
+ Configuration = pool.get('purchase.configuration')
+ config = Configuration(1)
+ supply_period = config.get_multivalue(
+ 'supply_period', company=self.company.id)
+ return supply_period or datetime.timedelta(1)
diff --git a/modules/stock_supply/product.xml b/modules/stock_supply/product.xml
new file mode 100644
index 0000000000..db24724809
--- /dev/null
+++ b/modules/stock_supply/product.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Order Points
+ stock.order_point
+
+
+
+ form_relate
+ product.product,-1
+
+
+
+
+ Order Points
+ stock.order_point
+
+
+
+ form_relate
+ product.template,-1
+
+
+
+
diff --git a/modules/stock_supply/purchase_request.py b/modules/stock_supply/purchase_request.py
new file mode 100644
index 0000000000..dad209b235
--- /dev/null
+++ b/modules/stock_supply/purchase_request.py
@@ -0,0 +1,347 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+import operator
+from collections import defaultdict
+
+from trytond.model import ModelSQL, ValueMixin, fields
+from trytond.transaction import Transaction
+from trytond.pool import Pool, PoolMeta
+from trytond.tools import grouped_slice
+
+supply_period = fields.TimeDelta("Supply Period")
+
+
+class PurchaseConfiguration(metaclass=PoolMeta):
+ __name__ = 'purchase.configuration'
+ supply_period = fields.MultiValue(supply_period)
+
+
+class PurchaseConfigurationSupplyPeriod(ModelSQL, ValueMixin):
+ "Purchase Configuration Supply Period"
+ __name__ = 'purchase.configuration.supply_period'
+ supply_period = supply_period
+
+
+class PurchaseRequest(metaclass=PoolMeta):
+ 'Purchase Request'
+ __name__ = 'purchase.request'
+
+ @classmethod
+ def _get_origin(cls):
+ origins = super(PurchaseRequest, cls)._get_origin()
+ return origins | {'stock.order_point'}
+
+ @classmethod
+ def generate_requests(cls, products=None, warehouses=None):
+ """
+ For each product compute the purchase request that must be
+ created today to meet product outputs.
+
+ If products is specified it will compute the purchase requests
+ for the selected products.
+
+ If warehouses is specified it will compute the purchase request
+ necessary for the selected warehouses.
+ """
+ pool = Pool()
+ OrderPoint = pool.get('stock.order_point')
+ Product = pool.get('product.product')
+ Location = pool.get('stock.location')
+ User = pool.get('res.user')
+ company = User(Transaction().user).company
+
+ if warehouses is None:
+ # fetch warehouses:
+ warehouses = Location.search([
+ ('type', '=', 'warehouse'),
+ ])
+ warehouse_ids = [w.id for w in warehouses]
+ # fetch order points
+ order_points = OrderPoint.search([
+ ('warehouse_location', '!=', None),
+ ('company', '=', company.id if company else None),
+ ])
+ # index them by product
+ product2ops = {}
+ product2ops_other = {}
+ for order_point in order_points:
+ if order_point.type == 'purchase':
+ dict_ = product2ops
+ else:
+ dict_ = product2ops_other
+ dict_[
+ (order_point.warehouse_location.id, order_point.product.id)
+ ] = order_point
+
+ if products is None:
+ # fetch goods and assets
+ # ordered by ids to speedup reduce_ids in products_by_location
+ products = Product.search([
+ ('type', 'in', ['goods', 'assets']),
+ ('consumable', '=', False),
+ ('purchasable', '=', True),
+ ], order=[('id', 'ASC')])
+ product_ids = [p.id for p in products]
+ # aggregate product by minimum supply date
+ date2products = {}
+ for product in products:
+ min_date, max_date = cls.get_supply_dates(
+ product, company=company.id)
+ date2products.setdefault((min_date, max_date), []).append(product)
+
+ # compute requests
+ new_requests = []
+ for dates, dates_products in date2products.items():
+ min_date, max_date = dates
+ for sub_products in grouped_slice(dates_products):
+ sub_products = list(sub_products)
+ product_ids = [p.id for p in sub_products]
+ with Transaction().set_context(forecast=True,
+ stock_date_end=min_date or datetime.date.max):
+ pbl = Product.products_by_location(warehouse_ids,
+ with_childs=True, grouping_filter=(product_ids,))
+ for warehouse_id in warehouse_ids:
+ min_date_qties = defaultdict(int,
+ ((x, pbl.pop((warehouse_id, x), 0))
+ for x in product_ids))
+ # Do not compute shortage for product
+ # with different order point
+ product_ids = [
+ p.id for p in sub_products
+ if (warehouse_id, p.id) not in product2ops_other]
+ # Search for shortage between min-max
+ shortages = cls.get_shortage(warehouse_id, product_ids,
+ min_date, max_date, min_date_qties=min_date_qties,
+ order_points=product2ops)
+
+ for product in sub_products:
+ if product.id not in shortages:
+ continue
+ shortage_date, product_quantity = shortages[product.id]
+ if shortage_date is None or product_quantity is None:
+ continue
+ order_point = product2ops.get(
+ (warehouse_id, product.id))
+ # generate request values
+ request = cls.compute_request(product,
+ warehouse_id, shortage_date, product_quantity,
+ company, order_point)
+ new_requests.append(request)
+
+ # delete purchase requests without a purchase line
+ products = set(products)
+ reqs = cls.search([
+ ('state', '=', 'draft'),
+ ('origin', 'like', 'stock.order_point,%'),
+ ])
+ reqs = [r for r in reqs
+ if r.product in products and r.warehouse in warehouses]
+ cls.delete(reqs)
+ new_requests = cls.compare_requests(new_requests)
+
+ cls.create_requests(new_requests)
+
+ @classmethod
+ def create_requests(cls, new_requests):
+ to_save = []
+ for new_req in new_requests:
+ if new_req.supply_date == datetime.date.max:
+ new_req.supply_date = None
+ if new_req.computed_quantity > 0:
+ to_save.append(new_req)
+ cls.save(to_save)
+
+ @classmethod
+ def compare_requests(cls, new_requests):
+ """
+ Compare new_requests with already existing request to avoid
+ to re-create existing requests.
+ """
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ Request = pool.get('purchase.request')
+
+ requests = Request.search([
+ ('purchase_line.moves', '=', None),
+ ('purchase_line.purchase.state', '!=', 'cancelled'),
+ ('origin', 'like', 'stock.order_point,%'),
+ ])
+ # Fetch data from existing requests
+ existing_req = {}
+ for request in requests:
+ pline = request.purchase_line
+ # Skip incoherent request
+ if (request.product != pline.product
+ or request.warehouse != pline.purchase.warehouse):
+ continue
+ # Take smallest amount between request and purchase line
+ pline_qty = Uom.compute_qty(pline.unit, pline.quantity,
+ pline.product.default_uom, round=False)
+ quantity = min(request.computed_quantity, pline_qty)
+
+ existing_req.setdefault(
+ (request.product.id, request.warehouse.id),
+ []).append({
+ 'supply_date': (
+ request.supply_date or datetime.date.max),
+ 'quantity': quantity,
+ })
+
+ for i in existing_req.values():
+ i.sort(key=lambda r: r['supply_date'])
+
+ # Update new requests to take existing requests into account
+ new_requests.sort(key=operator.attrgetter('supply_date'))
+ for new_req in new_requests:
+ for old_req in existing_req.get(
+ (new_req.product.id, new_req.warehouse.id), []):
+ if old_req['supply_date'] <= new_req.supply_date:
+ new_req.computed_quantity = max(0.0,
+ new_req.computed_quantity - old_req['quantity'])
+ new_req.quantity = Uom.compute_qty(
+ new_req.product.default_uom, new_req.computed_quantity,
+ new_req.uom, round=False)
+ new_req.quantity = new_req.uom.ceil(new_req.quantity)
+ old_req['quantity'] = max(0.0,
+ old_req['quantity'] - new_req.computed_quantity)
+ else:
+ break
+
+ return new_requests
+
+ @classmethod
+ def get_supply_dates(cls, product, **pattern):
+ """
+ Return the interval of earliest supply dates for a product.
+ """
+ Date = Pool().get('ir.date')
+
+ min_date = None
+ max_date = None
+ today = Date.today()
+
+ for product_supplier in product.product_suppliers_used(**pattern):
+ supply_date = product_supplier.compute_supply_date(date=today)
+ if supply_date == datetime.date.max:
+ continue
+ next_day = today + product_supplier.get_supply_period()
+ next_supply_date = product_supplier.compute_supply_date(
+ date=next_day)
+ if (not min_date) or supply_date < min_date:
+ min_date = supply_date
+ if (not max_date) or next_supply_date > max_date:
+ max_date = next_supply_date
+
+ if not min_date:
+ min_date = datetime.date.max
+ max_date = datetime.date.max
+
+ return (min_date, max_date)
+
+ @classmethod
+ def compute_request(cls, product, location_id, shortage_date,
+ product_quantity, company, order_point=None,
+ supplier_pattern=None):
+ """
+ Return the value of the purchase request which will answer to
+ the needed quantity at the given date. I.e: the latest
+ purchase date, the expected supply date and the prefered
+ supplier.
+ """
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ Request = pool.get('purchase.request')
+
+ if supplier_pattern is None:
+ supplier_pattern = {}
+ else:
+ supplier_pattern = supplier_pattern.copy()
+ supplier_pattern['company'] = company.id
+
+ supplier, purchase_date = cls.find_best_supplier(product,
+ shortage_date, **supplier_pattern)
+
+ uom = product.purchase_uom or product.default_uom
+ target_quantity = order_point.target_quantity if order_point else 0.0
+ computed_quantity = target_quantity - product_quantity
+ product_quantity = uom.ceil(product_quantity)
+ quantity = Uom.compute_qty(
+ product.default_uom, computed_quantity, uom, round=False)
+ quantity = uom.ceil(quantity)
+
+ if order_point:
+ origin = 'stock.order_point,%s' % order_point.id
+ else:
+ origin = 'stock.order_point,-1'
+ return Request(product=product,
+ party=supplier and supplier or None,
+ quantity=quantity,
+ uom=uom,
+ computed_quantity=computed_quantity,
+ computed_uom=product.default_uom,
+ purchase_date=purchase_date,
+ supply_date=shortage_date,
+ stock_level=product_quantity,
+ company=company,
+ warehouse=location_id,
+ origin=origin,
+ )
+
+ @classmethod
+ def get_shortage(cls, location_id, product_ids, min_date, max_date,
+ min_date_qties, order_points):
+ """
+ Return for each product the first date between min_date and max_date
+ where the stock quantity is less than the minimal quantity and the
+ smallest stock quantity in the interval or None if there is no date
+ where stock quantity is less than the minimal quantity.
+
+ The minimal quantity comes from the order point or is zero.
+
+ min_date_qty is the quantities for each products at the min_date.
+ order_points is a dictionary that links products to order point.
+ """
+ Product = Pool().get('product.product')
+
+ res_dates = {}
+ res_qties = {}
+
+ min_quantities = {}
+ for product_id in product_ids:
+ order_point = order_points.get((location_id, product_id))
+ if order_point:
+ min_quantities[product_id] = order_point.min_quantity
+ else:
+ min_quantities[product_id] = 0.0
+
+ current_date = min_date
+ current_qties = min_date_qties.copy()
+ while (current_date < max_date) or (current_date == min_date):
+ for product_id in product_ids:
+ current_qty = current_qties[product_id]
+ min_quantity = min_quantities[product_id]
+ res_qty = res_qties.get(product_id)
+ res_date = res_dates.get(product_id)
+ if min_quantity is not None and current_qty < min_quantity:
+ if not res_date:
+ res_dates[product_id] = current_date
+ if (not res_qty) or (current_qty < res_qty):
+ res_qties[product_id] = current_qty
+
+ if current_date == datetime.date.max:
+ break
+ current_date += datetime.timedelta(1)
+
+ # Update current quantities with next moves
+ with Transaction().set_context(forecast=True,
+ stock_date_start=current_date,
+ stock_date_end=current_date):
+ pbl = Product.products_by_location([location_id],
+ with_childs=True, grouping_filter=(product_ids,))
+ for key, qty in pbl.items():
+ _, product_id = key
+ current_qties[product_id] += qty
+
+ return dict((x, (res_dates.get(x), res_qties.get(x)))
+ for x in product_ids)
diff --git a/modules/stock_supply/purchase_request.xml b/modules/stock_supply/purchase_request.xml
new file mode 100644
index 0000000000..ea8a161d8f
--- /dev/null
+++ b/modules/stock_supply/purchase_request.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ purchase.configuration
+
+ purchase_configuration_form
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply/setup.py b/modules/stock_supply/setup.py
new file mode 100755
index 0000000000..bc8e69143e
--- /dev/null
+++ b/modules/stock_supply/setup.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_stock_supply'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = ['python-sql']
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [get_require_version('proteus')]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module for stock supply',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/stock_supply',
+ },
+ keywords='tryton stock supply',
+ package_dir={'trytond.modules.stock_supply': '.'},
+ packages=(
+ ['trytond.modules.stock_supply']
+ + ['trytond.modules.stock_supply.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.stock_supply': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ stock_supply = trytond.modules.stock_supply
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/stock_supply/shipment.py b/modules/stock_supply/shipment.py
new file mode 100644
index 0000000000..f033fab2f3
--- /dev/null
+++ b/modules/stock_supply/shipment.py
@@ -0,0 +1,168 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+
+from trytond.model import ModelView, ModelSQL
+from trytond.transaction import Transaction
+from trytond.pool import Pool
+
+
+class ShipmentInternal(ModelSQL, ModelView):
+ __name__ = 'stock.shipment.internal'
+
+ @classmethod
+ def generate_internal_shipment(cls, clean=True):
+ """
+ Generate internal shipments to meet order points defined on
+ non-warehouse location.
+
+ If clean is set, it will remove all previous requests.
+ """
+ pool = Pool()
+ OrderPoint = pool.get('stock.order_point')
+ Location = pool.get('stock.location')
+ Product = pool.get('product.product')
+ Date = pool.get('ir.date')
+ User = pool.get('res.user')
+ Move = pool.get('stock.move')
+ LeadTime = pool.get('stock.location.lead_time')
+
+ user_record = User(Transaction().user)
+ today = Date.today()
+ lead_time = LeadTime.get_max_lead_time()
+
+ if clean:
+ reqs = cls.search([
+ ('state', '=', 'request'),
+ ])
+ cls.delete(reqs)
+
+ # fetch quantities on order points
+ order_points = OrderPoint.search([
+ ('type', '=', 'internal'),
+ ])
+ id2product = {}
+ product2op = {}
+ id2location = {}
+ for op in order_points:
+ id2product[op.product.id] = op.product
+ product2op[
+ (op.storage_location.id, op.product.id)
+ ] = op
+ id2location[op.storage_location.id] = op.storage_location
+ implicit_locations = Location.search(['OR',
+ ('provisioning_location', '!=', None),
+ ('overflowing_location', '!=', None),
+ ])
+ id2location.update({l.id: l for l in implicit_locations})
+ location_ids = list(id2location.keys())
+
+ # ordered by ids to speedup reduce_ids in products_by_location
+ if implicit_locations:
+ products = Product.search([
+ ('type', 'in', ['goods', 'assets']),
+ ], order=[('id', 'ASC')])
+ product_ids = [p.id for p in products]
+ else:
+ product_ids = list(id2product.keys())
+ product_ids.sort()
+
+ with Transaction().set_context(forecast=True, stock_date_end=today):
+ pbl = Product.products_by_location(
+ location_ids, with_childs=True, grouping_filter=(product_ids,))
+
+ shipments = []
+ date = today
+ end_date = date + lead_time
+ current_qties = pbl.copy()
+ while date <= end_date:
+ # Create a list of moves to create
+ moves = {}
+ for location in id2location.values():
+ for product_id in product_ids:
+ qty = current_qties.get((location.id, product_id), 0)
+ op = product2op.get((location.id, product_id))
+ if op:
+ min_qty, max_qty = op.min_quantity, op.max_quantity
+ target_qty = op.target_quantity
+ prov_location = op.provisioning_location
+ over_location = op.overflowing_location
+ elif (location
+ and (location.provisioning_location
+ or location.overflowing_location)):
+ target_qty = 0
+ min_qty = 0 if location.provisioning_location else None
+ max_qty = 0 if location.overflowing_location else None
+ prov_location = location.provisioning_location
+ over_location = location.overflowing_location
+ else:
+ continue
+
+ change_qty = 0
+ if min_qty is not None and qty < min_qty:
+ from_loc = prov_location.id
+ to_loc = location.id
+ change_qty = target_qty - qty
+ elif max_qty is not None and qty > max_qty:
+ from_loc = location.id
+ to_loc = over_location.id
+ change_qty = qty - target_qty
+
+ if change_qty:
+ key = (from_loc, to_loc, product_id)
+ moves[key] = change_qty
+ current_qties[(from_loc, product_id)] -= change_qty
+ current_qties[(to_loc, product_id)] += change_qty
+
+ # Group moves by {from,to}_location
+ to_create = {}
+ for key, qty in moves.items():
+ from_location, to_location, product = key
+ to_create.setdefault(
+ (from_location, to_location), []).append((product, qty))
+ # Create shipments and moves
+ for locations, moves in to_create.items():
+ from_location, to_location = locations
+ shipment = cls(
+ from_location=from_location,
+ to_location=to_location,
+ planned_date=date,
+ state='request',
+ )
+ shipment_moves = []
+ for move in moves:
+ product_id, qty = move
+ product = id2product.setdefault(
+ product_id, Product(product_id))
+ shipment_moves.append(Move(
+ from_location=from_location,
+ to_location=to_location,
+ planned_date=date,
+ product=product,
+ quantity=qty,
+ uom=product.default_uom,
+ company=user_record.company,
+ ))
+ shipment.moves = shipment_moves
+ shipment.planned_start_date = (
+ shipment.on_change_with_planned_start_date())
+ shipments.append(shipment)
+ date += datetime.timedelta(1)
+
+ # Update quantities with next moves
+ with Transaction().set_context(
+ forecast=True,
+ stock_date_start=date,
+ stock_date_end=date):
+ pbl = Product.products_by_location(
+ location_ids,
+ with_childs=True,
+ grouping_filter=(product_ids,))
+ for key, qty in pbl.items():
+ current_qties[key] += qty
+
+ if shipments:
+ cls.save(shipments)
+ # Split moves through transit to get accurate dates
+ cls._set_transit(shipments)
+ return shipments
diff --git a/modules/stock_supply/stock.py b/modules/stock_supply/stock.py
new file mode 100644
index 0000000000..a5614d5e26
--- /dev/null
+++ b/modules/stock_supply/stock.py
@@ -0,0 +1,115 @@
+# 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.i18n import gettext
+from trytond.pool import Pool
+from trytond.model import ModelView
+from trytond.transaction import Transaction
+from trytond.wizard import (Wizard, StateView, StateAction, StateTransition,
+ Button)
+
+from .exceptions import SupplyWarning
+
+
+class Supply(Wizard):
+ "Supply Stock"
+ __name__ = 'stock.supply'
+ start = StateView(
+ 'stock.supply.start',
+ 'stock_supply.supply_start_view_form', [
+ Button('Cancel', 'end', 'tryton-cancel'),
+ Button('Create', 'create_', 'tryton-ok', default=True),
+ ])
+ create_ = StateTransition()
+ internal = StateAction('stock.act_shipment_internal_form')
+ purchase = StateAction('purchase_request.act_purchase_request_form')
+
+ @classmethod
+ def types(cls):
+ return ['internal', 'purchase']
+
+ @classmethod
+ def next_action(cls, name):
+ types = cls.types()
+ try:
+ return types[types.index(name) + 1]
+ except IndexError:
+ return 'end'
+
+ def transition_create_(self):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ ShipmentInternal = pool.get('stock.shipment.internal')
+ Date = pool.get('ir.date')
+ Warning = pool.get('res.user.warning')
+ today = Date.today()
+ with Transaction().set_context(_check_access=True):
+ if Move.search([
+ ('from_location.type', '=', 'supplier'),
+ ('to_location.type', '=', 'storage'),
+ ('state', '=', 'draft'),
+ ('planned_date', '<', today),
+ ], order=[]):
+ name = '%s.supplier@%s' % (self.__name__, today)
+ if Warning.check(name):
+ raise SupplyWarning(name,
+ gettext('stock_supply.msg_late_supplier_moves'))
+ if Move.search([
+ ('from_location.type', '=', 'storage'),
+ ('to_location.type', '=', 'customer'),
+ ('state', '=', 'draft'),
+ ('planned_date', '<', today),
+ ], order=[]):
+ name = '%s..customer@%s' % (self.__name__, today)
+ if Warning.check(name):
+ raise SupplyWarning(name,
+ gettext('stock_supply.msg_late_customer_moves'))
+
+ first = True
+ created = False
+ while created or first:
+ created = False
+ for type_ in self.types():
+ created |= bool(getattr(self, 'generate_%s' % type_)(first))
+ first = False
+
+ # Remove transit split of request
+ with Transaction().set_context(_check_access=True):
+ shipments = ShipmentInternal.search([
+ ('state', '=', 'request'),
+ ])
+ Move.delete([m for s in shipments for m in s.moves
+ if m.from_location == s.transit_location])
+ for shipment in shipments:
+ Move.write([m for m in shipment.moves], {
+ 'from_location': shipment.from_location.id,
+ 'to_location': shipment.to_location.id,
+ 'planned_date': shipment.planned_date,
+ })
+
+ return self.types()[0]
+
+ def generate_internal(self, clean):
+ pool = Pool()
+ ShipmentInternal = pool.get('stock.shipment.internal')
+ return ShipmentInternal.generate_internal_shipment(clean=clean)
+
+ def transition_internal(self):
+ return self.next_action('internal')
+
+ @property
+ def _purchase_parameters(self):
+ return {}
+
+ def generate_purchase(self, clean):
+ pool = Pool()
+ PurchaseRequest = pool.get('purchase.request')
+ PurchaseRequest.generate_requests(**self._purchase_parameters)
+ return False
+
+ def transition_purchase(self):
+ return self.next_action('purchase')
+
+
+class SupplyStart(ModelView):
+ "Supply Stock"
+ __name__ = 'stock.supply.start'
diff --git a/modules/stock_supply/stock.xml b/modules/stock_supply/stock.xml
new file mode 100644
index 0000000000..863cbf033d
--- /dev/null
+++ b/modules/stock_supply/stock.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Supply Stock
+ stock.supply
+
+
+
+
+
+
+
+
+
+
+
+
+
+ stock.supply.start
+ form
+ supply_start_form
+
+
+
diff --git a/modules/stock_supply/tests/__init__.py b/modules/stock_supply/tests/__init__.py
new file mode 100644
index 0000000000..875434dc1e
--- /dev/null
+++ b/modules/stock_supply/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.stock_supply.tests.test_stock_supply import suite
+except ImportError:
+ from .test_stock_supply import suite
+
+__all__ = ['suite']
diff --git a/modules/stock_supply/tests/scenario_stock_internal_supply.rst b/modules/stock_supply/tests/scenario_stock_internal_supply.rst
new file mode 100644
index 0000000000..acd193eea4
--- /dev/null
+++ b/modules/stock_supply/tests/scenario_stock_internal_supply.rst
@@ -0,0 +1,192 @@
+===========================
+Stock Shipment Out Scenario
+===========================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules, set_user
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+ >>> today = datetime.date.today()
+
+Activate modules::
+
+ >>> config = activate_modules('stock_supply')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create stock admin user::
+
+ >>> User = Model.get('res.user')
+ >>> Group = Model.get('res.group')
+ >>> stock_admin_user = User()
+ >>> stock_admin_user.name = 'Stock Admin'
+ >>> stock_admin_user.login = 'stock_admin'
+ >>> stock_admin_group, = Group.find([('name', '=', 'Stock Administration')])
+ >>> stock_admin_user.groups.append(stock_admin_group)
+ >>> stock_admin_user.save()
+
+Create stock user::
+
+ >>> stock_user = User()
+ >>> stock_user.name = 'Stock'
+ >>> stock_user.login = 'stock'
+ >>> stock_group, = Group.find([('name', '=', 'Stock')])
+ >>> stock_user.groups.append(stock_group)
+ >>> stock_user.save()
+
+Create product user::
+
+ >>> product_admin_user = User()
+ >>> product_admin_user.name = 'Product'
+ >>> product_admin_user.login = 'product'
+ >>> product_admin_group, = Group.find([
+ ... ('name', '=', 'Product Administration')
+ ... ])
+ >>> product_admin_user.groups.append(product_admin_group)
+ >>> product_admin_user.save()
+
+Create product::
+
+ >>> set_user(product_admin_user)
+ >>> ProductUom = Model.get('product.uom')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'Product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> set_user(stock_admin_user)
+ >>> Location = Model.get('stock.location')
+ >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+ >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+ >>> output_loc, = Location.find([('code', '=', 'OUT')])
+ >>> storage_loc, = Location.find([('code', '=', 'STO')])
+ >>> lost_loc, = Location.find([('type', '=', 'lost_found')])
+
+Create provisioning location::
+
+ >>> Location = Model.get('stock.location')
+ >>> provisioning_loc = Location()
+ >>> provisioning_loc.name = 'Provisioning Location'
+ >>> provisioning_loc.type = 'storage'
+ >>> provisioning_loc.parent = warehouse_loc
+ >>> provisioning_loc.save()
+
+Create a new storage location::
+
+ >>> sec_storage_loc = Location()
+ >>> sec_storage_loc.name = 'Second Storage'
+ >>> sec_storage_loc.type = 'storage'
+ >>> sec_storage_loc.parent = warehouse_loc
+ >>> sec_storage_loc.provisioning_location = provisioning_loc
+ >>> sec_storage_loc.save()
+
+Create internal order point::
+
+ >>> OrderPoint = Model.get('stock.order_point')
+ >>> order_point = OrderPoint()
+ >>> order_point.product = product
+ >>> order_point.storage_location = storage_loc
+ >>> order_point.provisioning_location = provisioning_loc
+ >>> order_point.type = 'internal'
+ >>> order_point.min_quantity = 10
+ >>> order_point.target_quantity = 15
+ >>> order_point.save()
+
+Create inventory to add enough quantity in Provisioning Location::
+
+ >>> set_user(stock_user)
+ >>> Inventory = Model.get('stock.inventory')
+ >>> inventory = Inventory()
+ >>> inventory.location = provisioning_loc
+ >>> inventory_line = inventory.lines.new(product=product)
+ >>> inventory_line.quantity = 100.0
+ >>> inventory_line.expected_quantity = 0.0
+ >>> inventory.click('confirm')
+ >>> inventory.state
+ 'done'
+
+Execute internal supply::
+
+ >>> ShipmentInternal = Model.get('stock.shipment.internal')
+ >>> set_user(stock_admin_user)
+ >>> Wizard('stock.supply').execute('create_')
+ >>> set_user(stock_user)
+ >>> shipment, = ShipmentInternal.find([])
+ >>> shipment.state
+ 'request'
+ >>> len(shipment.moves)
+ 1
+ >>> move, = shipment.moves
+ >>> move.product.template.name
+ 'Product'
+ >>> move.quantity
+ 15.0
+ >>> move.from_location.name
+ 'Provisioning Location'
+ >>> move.to_location.code
+ 'STO'
+
+Create negative quantity in Second Storage::
+
+ >>> Move = Model.get('stock.move')
+ >>> move = Move()
+ >>> move.product = product
+ >>> move.quantity = 10
+ >>> move.from_location = sec_storage_loc
+ >>> move.to_location = lost_loc
+ >>> move.click('do')
+ >>> move.state
+ 'done'
+
+Execute internal supply::
+
+ >>> set_user(stock_admin_user)
+ >>> Wizard('stock.supply').execute('create_')
+ >>> set_user(stock_user)
+ >>> shipment, = ShipmentInternal.find(
+ ... [('to_location', '=', sec_storage_loc.id)])
+ >>> shipment.state
+ 'request'
+ >>> len(shipment.moves)
+ 1
+ >>> move, = shipment.moves
+ >>> move.product.template.name
+ 'Product'
+ >>> move.quantity
+ 10.0
+ >>> move.from_location.name
+ 'Provisioning Location'
+ >>> move.to_location.name
+ 'Second Storage'
+
+Create stock_supply cron and execute it::
+
+ >>> Cron = Model.get('ir.cron')
+ >>> admin_user, = User.find([('login', '=', 'admin')])
+ >>> set_user(admin_user)
+ >>> shipment.delete()
+ >>> cron = Cron(method='stock.order_point|supply_stock')
+ >>> cron.interval_number = 1
+ >>> cron.interval_type = 'months'
+ >>> cron.click('run_once')
+ >>> shipment, = ShipmentInternal.find(
+ ... [('to_location', '=', sec_storage_loc.id)])
+ >>> shipment.state
+ 'request'
diff --git a/modules/stock_supply/tests/scenario_stock_internal_supply_lead_time.rst b/modules/stock_supply/tests/scenario_stock_internal_supply_lead_time.rst
new file mode 100644
index 0000000000..446cc0584f
--- /dev/null
+++ b/modules/stock_supply/tests/scenario_stock_internal_supply_lead_time.rst
@@ -0,0 +1,143 @@
+========================================
+Stock Internal Supply Lead Time Scenario
+========================================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+ >>> today = datetime.date.today()
+ >>> tomorrow = today + relativedelta(days=1)
+
+Activate modules::
+
+ >>> config = activate_modules('stock_supply')
+
+Create customer::
+
+ >>> Party = Model.get('party.party')
+ >>> customer = Party(name='Customer')
+ >>> customer.save()
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'Product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> Location = Model.get('stock.location')
+ >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+ >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+ >>> lost_loc, = Location.find([('type', '=', 'lost_found')])
+
+Create second warehouse::
+
+ >>> sec_warehouse_loc, = warehouse_loc.duplicate()
+
+Add lead time between warehouses::
+
+ >>> LeadTime = Model.get('stock.location.lead_time')
+ >>> lead_time = LeadTime()
+ >>> lead_time.warehouse_from = warehouse_loc
+ >>> lead_time.warehouse_to = sec_warehouse_loc
+ >>> lead_time.lead_time = datetime.timedelta(1)
+ >>> lead_time.save()
+
+Create internal order point::
+
+ >>> OrderPoint = Model.get('stock.order_point')
+ >>> order_point = OrderPoint()
+ >>> order_point.product = product
+ >>> order_point.storage_location = sec_warehouse_loc.storage_location
+ >>> order_point.provisioning_location = warehouse_loc.storage_location
+ >>> order_point.type = 'internal'
+ >>> order_point.min_quantity = 10
+ >>> order_point.target_quantity = 15
+ >>> order_point.save()
+
+Create inventory to add enough quantity in first warehouse::
+
+ >>> Inventory = Model.get('stock.inventory')
+ >>> inventory = Inventory()
+ >>> inventory.location = warehouse_loc.storage_location
+ >>> inventory_line = inventory.lines.new(product=product)
+ >>> inventory_line.quantity = 100.0
+ >>> inventory_line.expected_quantity = 0.0
+ >>> inventory.click('confirm')
+ >>> inventory.state
+ 'done'
+
+Create needs for tomorrow in second warehouse::
+
+ >>> ShipmentOut = Model.get('stock.shipment.out')
+ >>> shipment = ShipmentOut()
+ >>> shipment.planned_date = tomorrow
+ >>> shipment.customer = customer
+ >>> shipment.warehouse = sec_warehouse_loc
+ >>> move = shipment.outgoing_moves.new()
+ >>> move.product = product
+ >>> move.quantity = 10
+ >>> move.from_location = sec_warehouse_loc.output_location
+ >>> move.to_location = customer_loc
+ >>> move.unit_price = Decimal('20')
+ >>> shipment.click('wait')
+ >>> shipment.state
+ 'waiting'
+
+Execute internal supply::
+
+ >>> ShipmentInternal = Model.get('stock.shipment.internal')
+ >>> Wizard('stock.supply').execute('create_')
+ >>> shipments = ShipmentInternal.find([], order=[('planned_date', 'ASC')])
+ >>> len(shipments)
+ 2
+ >>> first, second = shipments
+
+ >>> first.planned_date == today
+ True
+ >>> first.state
+ 'request'
+ >>> len(first.moves)
+ 1
+ >>> move, = first.moves
+ >>> move.from_location == warehouse_loc.storage_location
+ True
+ >>> move.to_location == sec_warehouse_loc.storage_location
+ True
+ >>> move.quantity
+ 15.0
+
+ >>> second.planned_date == tomorrow
+ True
+ >>> second.state
+ 'request'
+ >>> len(second.moves)
+ 1
+ >>> move, = second.moves
+ >>> move.from_location == warehouse_loc.storage_location
+ True
+ >>> move.to_location == sec_warehouse_loc.storage_location
+ True
+ >>> move.quantity
+ 10.0
diff --git a/modules/stock_supply/tests/scenario_stock_internal_supply_overflow.rst b/modules/stock_supply/tests/scenario_stock_internal_supply_overflow.rst
new file mode 100644
index 0000000000..a8e427b5d5
--- /dev/null
+++ b/modules/stock_supply/tests/scenario_stock_internal_supply_overflow.rst
@@ -0,0 +1,145 @@
+====================================================
+Stock supply scenario: Internal supply with overflow
+====================================================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+ >>> today = datetime.date.today()
+
+Activate modules::
+
+ >>> config = activate_modules('stock_supply')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'Product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> Location = Model.get('stock.location')
+ >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+ >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+ >>> output_loc, = Location.find([('code', '=', 'OUT')])
+ >>> storage_loc, = Location.find([('code', '=', 'STO')])
+ >>> lost_loc, = Location.find([('type', '=', 'lost_found')])
+
+Using order point to control the overflow
+-----------------------------------------
+
+Create the overflow location::
+
+ >>> overflow_loc = Location()
+ >>> overflow_loc.name = 'Overflow Location'
+ >>> overflow_loc.type = 'storage'
+ >>> overflow_loc.parent = warehouse_loc
+ >>> overflow_loc.save()
+
+Create the overflowed location::
+
+ >>> overflowed_storage_loc = Location()
+ >>> overflowed_storage_loc.name = 'Overflowed Location'
+ >>> overflowed_storage_loc.type = 'storage'
+ >>> overflowed_storage_loc.parent = warehouse_loc
+ >>> overflowed_storage_loc.save()
+
+Create an internal order point::
+
+ >>> OrderPoint = Model.get('stock.order_point')
+ >>> overflow_order_point = OrderPoint()
+ >>> overflow_order_point.product = product
+ >>> overflow_order_point.storage_location = overflowed_storage_loc
+ >>> overflow_order_point.overflowing_location = overflow_loc
+ >>> overflow_order_point.type = 'internal'
+ >>> overflow_order_point.max_quantity = 80
+ >>> overflow_order_point.target_quantity = 60
+ >>> overflow_order_point.save()
+
+Put too much quantity in the overflowed location::
+
+ >>> Move = Model.get('stock.move')
+ >>> move = Move()
+ >>> move.product = product
+ >>> move.quantity = 100
+ >>> move.from_location = lost_loc
+ >>> move.to_location = overflowed_storage_loc
+ >>> move.click('do')
+
+Execute internal supply::
+
+ >>> ShipmentInternal = Model.get('stock.shipment.internal')
+ >>> Wizard('stock.supply').execute('create_')
+ >>> shipment, = ShipmentInternal.find([
+ ... ('to_location', '=', overflow_loc.id),
+ ... ])
+ >>> shipment.state
+ 'request'
+ >>> move, = shipment.moves
+ >>> move.product.id == product.id
+ True
+ >>> move.quantity
+ 40.0
+ >>> move.from_location.id == overflowed_storage_loc.id
+ True
+ >>> move.to_location.id == overflow_loc.id
+ True
+
+Using an overflow location
+--------------------------
+
+Create the overflowed location::
+
+ >>> sec_overflowed_storage_loc = Location()
+ >>> sec_overflowed_storage_loc.name = 'Second Overflowed Location'
+ >>> sec_overflowed_storage_loc.type = 'storage'
+ >>> sec_overflowed_storage_loc.parent = warehouse_loc
+ >>> sec_overflowed_storage_loc.overflowing_location = overflow_loc
+ >>> sec_overflowed_storage_loc.save()
+
+Create positive quantity in this location::
+
+ >>> move = Move()
+ >>> move.product = product
+ >>> move.quantity = 10
+ >>> move.from_location = lost_loc
+ >>> move.to_location = sec_overflowed_storage_loc
+ >>> move.click('do')
+
+Execute internal supply::
+
+ >>> Wizard('stock.supply').execute('create_')
+ >>> shipment, = ShipmentInternal.find(
+ ... [('from_location', '=', sec_overflowed_storage_loc.id)])
+ >>> shipment.state
+ 'request'
+ >>> move, = shipment.moves
+ >>> move.product.id == product.id
+ True
+ >>> move.quantity
+ 10.0
+ >>> move.from_location.id == sec_overflowed_storage_loc.id
+ True
+ >>> move.to_location.id == overflow_loc.id
+ True
diff --git a/modules/stock_supply/tests/scenario_stock_supply_purchase_request.rst b/modules/stock_supply/tests/scenario_stock_supply_purchase_request.rst
new file mode 100644
index 0000000000..85b7db7815
--- /dev/null
+++ b/modules/stock_supply/tests/scenario_stock_supply_purchase_request.rst
@@ -0,0 +1,234 @@
+=========================
+Purchase Request Scenario
+=========================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules, set_user
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+ >>> from trytond.modules.account.tests.tools import (create_chart,
+ ... get_accounts)
+ >>> today = datetime.date.today()
+
+Activate modules::
+
+ >>> config = activate_modules('stock_supply')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create chart of accounts::
+
+ >>> _ = create_chart(company)
+ >>> accounts = get_accounts(company)
+ >>> expense = accounts['expense']
+
+Create parties::
+
+ >>> Party = Model.get('party.party')
+ >>> customer = Party(name='Customer')
+ >>> customer.save()
+ >>> supplier = Party(name='Supplier')
+ >>> supplier.save()
+
+Create stock admin user::
+
+ >>> User = Model.get('res.user')
+ >>> Group = Model.get('res.group')
+ >>> stock_admin_user = User()
+ >>> stock_admin_user.name = 'Stock Admin'
+ >>> stock_admin_user.login = 'stock_admin'
+ >>> stock_admin_group, = Group.find([('name', '=', 'Stock Administration')])
+ >>> stock_admin_user.groups.append(stock_admin_group)
+ >>> stock_admin_user.save()
+
+Create stock user::
+
+ >>> stock_user = User()
+ >>> stock_user.name = 'Stock'
+ >>> stock_user.login = 'stock'
+ >>> stock_group, = Group.find([('name', '=', 'Stock')])
+ >>> stock_user.groups.append(stock_group)
+ >>> stock_user.save()
+
+Create product user::
+
+ >>> product_admin_user = User()
+ >>> product_admin_user.name = 'Product'
+ >>> product_admin_user.login = 'product'
+ >>> product_admin_group, = Group.find([
+ ... ('name', '=', 'Product Administration')
+ ... ])
+ >>> product_admin_user.groups.append(product_admin_group)
+ >>> product_admin_user.save()
+
+Create purchase user::
+
+ >>> purchase_user = User()
+ >>> purchase_user.name = 'Purchase'
+ >>> purchase_user.login = 'purchase'
+ >>> purchase_groups = Group.find(['OR',
+ ... ('name', '=', "Purchase"),
+ ... ('name', '=', "Purchase Request"),
+ ... ])
+ >>> purchase_user.groups.extend(purchase_groups)
+ >>> purchase_user.save()
+
+
+Create account category::
+
+ >>> ProductCategory = Model.get('product.category')
+ >>> account_category = ProductCategory(name="Account Category")
+ >>> account_category.accounting = True
+ >>> account_category.account_expense = expense
+ >>> account_category.save()
+
+Create product::
+
+ >>> set_user(product_admin_user)
+ >>> ProductUom = Model.get('product.uom')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'Product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> template.purchasable = True
+ >>> template.account_category = account_category
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> set_user(stock_admin_user)
+ >>> Location = Model.get('stock.location')
+ >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+ >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+ >>> output_loc, = Location.find([('code', '=', 'OUT')])
+ >>> storage_loc, = Location.find([('code', '=', 'STO')])
+
+Create a need for missing product::
+
+ >>> set_user(stock_user)
+ >>> ShipmentOut = Model.get('stock.shipment.out')
+ >>> shipment_out = ShipmentOut()
+ >>> shipment_out.planned_date = today
+ >>> shipment_out.effective_date = today
+ >>> shipment_out.customer = customer
+ >>> shipment_out.warehouse = warehouse_loc
+ >>> shipment_out.company = company
+ >>> move = shipment_out.outgoing_moves.new()
+ >>> move.product = product
+ >>> move.uom = unit
+ >>> move.quantity = 1
+ >>> move.from_location = output_loc
+ >>> move.to_location = customer_loc
+ >>> move.company = company
+ >>> move.unit_price = Decimal('1')
+ >>> move.currency = company.currency
+ >>> shipment_out.click('wait')
+
+There is no purchase request::
+
+ >>> PurchaseRequest = Model.get('purchase.request')
+ >>> set_user(purchase_user)
+ >>> PurchaseRequest.find([])
+ []
+
+Create the purchase request::
+
+ >>> set_user(stock_admin_user)
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+There is now a draft purchase request::
+
+ >>> set_user(purchase_user)
+ >>> pr, = PurchaseRequest.find([('state', '=', 'draft')])
+ >>> pr.product == product
+ True
+ >>> pr.quantity
+ 1.0
+
+Create an order point with negative minimal quantity::
+
+ >>> set_user(stock_admin_user)
+ >>> OrderPoint = Model.get('stock.order_point')
+ >>> order_point = OrderPoint()
+ >>> order_point.type = 'purchase'
+ >>> order_point.product = product
+ >>> order_point.warehouse_location = warehouse_loc
+ >>> order_point.min_quantity = -1
+ >>> order_point.target_quantity = 10
+ >>> order_point.save()
+
+Create production request::
+
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+There is no more production request::
+
+ >>> set_user(purchase_user)
+ >>> PurchaseRequest.find([])
+ []
+
+Set a postive minimal quantity on order point create purchase request::
+
+ >>> set_user(stock_admin_user)
+ >>> order_point.min_quantity = 5
+ >>> order_point.save()
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+There is now a draft purchase request::
+
+ >>> set_user(purchase_user)
+ >>> pr, = PurchaseRequest.find([('state', '=', 'draft')])
+ >>> pr.product == product
+ True
+ >>> pr.quantity
+ 11.0
+
+Using zero as minimal quantity on order point also creates purchase request::
+
+ >>> set_user(stock_admin_user)
+ >>> order_point.min_quantity = 0
+ >>> order_point.save()
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+There is now a draft purchase request::
+
+ >>> set_user(purchase_user)
+ >>> pr, = PurchaseRequest.find([('state', '=', 'draft')])
+ >>> pr.product == product
+ True
+ >>> pr.quantity
+ 11.0
+
+Re-run with purchased request::
+
+ >>> create_purchase = Wizard('purchase.request.create_purchase', [pr])
+ >>> create_purchase.form.party = supplier
+ >>> create_purchase.execute('start')
+ >>> pr.state
+ 'purchased'
+
+ >>> set_user(stock_admin_user)
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+ >>> set_user(purchase_user)
+ >>> len(PurchaseRequest.find([('state', '=', 'draft')]))
+ 0
diff --git a/modules/stock_supply/tests/test_stock_supply.py b/modules/stock_supply/tests/test_stock_supply.py
new file mode 100644
index 0000000000..e4d0548be1
--- /dev/null
+++ b/modules/stock_supply/tests/test_stock_supply.py
@@ -0,0 +1,189 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import unittest
+import doctest
+import datetime
+from decimal import Decimal
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.tests.test_tryton import doctest_teardown
+from trytond.tests.test_tryton import doctest_checker
+from trytond.pool import Pool
+
+from trytond.modules.company.tests import (
+ create_company, set_company, CompanyTestMixin)
+from trytond.modules.account.tests import create_chart
+
+DATES = [
+ # purchase date, lead time, supply date
+ (datetime.date(2011, 11, 21), datetime.timedelta(10),
+ datetime.date(2011, 12, 1)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(9),
+ datetime.date(2011, 11, 30)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(8),
+ datetime.date(2011, 11, 29)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(7),
+ datetime.date(2011, 11, 28)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(6),
+ datetime.date(2011, 11, 27)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(5),
+ datetime.date(2011, 11, 26)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(4),
+ datetime.date(2011, 11, 25)),
+ ]
+
+
+class StockSupplyTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test StockSupply module'
+ module = 'stock_supply'
+
+ def test_compute_supply_date(self):
+ 'Test compute_supply_date'
+ @with_transaction()
+ def run(purchase_date, lead_time, supply_date):
+ pool = Pool()
+ ProductSupplier = pool.get('purchase.product_supplier')
+ product_supplier = self.create_product_supplier(lead_time)
+ date = ProductSupplier.compute_supply_date(
+ product_supplier, purchase_date)
+ self.assertEqual(date, supply_date)
+ for purchase_date, lead_time, supply_date in DATES:
+ run(purchase_date, lead_time, supply_date)
+
+ def test_compute_purchase_date(self):
+ 'Test compute_purchase_date'
+ @with_transaction()
+ def run(purchase_date, lead_time, supply_date):
+ pool = Pool()
+ ProductSupplier = pool.get('purchase.product_supplier')
+ product_supplier = self.create_product_supplier(lead_time)
+ date = ProductSupplier.compute_purchase_date(
+ product_supplier, supply_date)
+ self.assertEqual(date, purchase_date)
+ for purchase_date, lead_time, supply_date in DATES:
+ run(purchase_date, lead_time, supply_date)
+
+ def create_product_supplier(self, lead_time):
+ '''
+ Create a Product with a Product Supplier
+
+ :param lead_time: timedelta needed to supply
+ :return: the id of the Product Supplier
+ '''
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ UomCategory = pool.get('product.uom.category')
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ Party = pool.get('party.party')
+ Account = pool.get('account.account')
+ ProductSupplier = pool.get('purchase.product_supplier')
+
+ uom_category, = UomCategory.create([{'name': 'Test'}])
+ uom, = Uom.create([{
+ 'name': 'Test',
+ 'symbol': 'T',
+ 'category': uom_category.id,
+ 'rate': 1.0,
+ 'factor': 1.0,
+ }])
+ template, = Template.create([{
+ 'name': 'ProductTest',
+ 'default_uom': uom.id,
+ 'list_price': Decimal(0),
+ }])
+ product, = Product.create([{
+ 'template': template.id,
+ }])
+ company = create_company()
+ with set_company(company):
+ create_chart(company)
+ receivable, = Account.search([
+ ('type.receivable', '=', True),
+ ('company', '=', company.id),
+ ])
+ payable, = Account.search([
+ ('type.payable', '=', True),
+ ('company', '=', company.id),
+ ])
+ supplier, = Party.create([{
+ 'name': 'supplier',
+ 'account_receivable': receivable.id,
+ 'account_payable': payable.id,
+ }])
+ product_supplier, = ProductSupplier.create([{
+ 'template': template.id,
+ 'company': company.id,
+ 'party': supplier.id,
+ 'lead_time': lead_time,
+ }])
+ return product_supplier
+
+ @with_transaction()
+ def test_order_point_location_searcher(self):
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ Location = pool.get('stock.location')
+ OrderPoint = pool.get('stock.order_point')
+ unit, = Uom.search([('symbol', '=', 'u')])
+ template, = Template.create([{
+ 'name': 'ProductTest',
+ 'type': 'goods',
+ 'default_uom': unit.id,
+ 'purchase_uom': unit.id,
+ 'list_price': Decimal(0),
+ 'purchasable': True,
+ }])
+ product, = Product.create([{
+ 'template': template.id,
+ }])
+
+ warehouse, = Location.search([('type', '=', 'warehouse')])
+ storage, = Location.search([('code', '=', 'STO')])
+
+ company = create_company()
+ with set_company(company):
+ order_point, = OrderPoint.create([{
+ 'product': product.id,
+ 'type': 'purchase',
+ 'warehouse_location': warehouse.id,
+ 'min_quantity': 0.0,
+ 'target_quantity': 5.0,
+ }])
+
+ for clause, result in [
+ (('location', '=', warehouse.name), [order_point]),
+ (('location', '=', 'storage'), []),
+ (('location', '!=', warehouse.name), []),
+ (('location', '!=', 'storage'), [order_point]),
+ ]:
+ self.assertListEqual(
+ OrderPoint.search(clause), result, msg=clause)
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ StockSupplyTestCase))
+ suite.addTests(doctest.DocFileSuite('scenario_stock_internal_supply.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_stock_internal_supply_lead_time.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_stock_supply_purchase_request.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_stock_internal_supply_overflow.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ return suite
diff --git a/modules/stock_supply/tox.ini b/modules/stock_supply/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/stock_supply/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/stock_supply/tryton.cfg b/modules/stock_supply/tryton.cfg
new file mode 100644
index 0000000000..08d14b2474
--- /dev/null
+++ b/modules/stock_supply/tryton.cfg
@@ -0,0 +1,18 @@
+[tryton]
+version=6.0.4
+depends:
+ account
+ ir
+ party
+ product
+ purchase
+ purchase_request
+ res
+ stock
+xml:
+ order_point.xml
+ purchase_request.xml
+ product.xml
+ location.xml
+ stock.xml
+ message.xml
diff --git a/modules/stock_supply/view/location_form.xml b/modules/stock_supply/view/location_form.xml
new file mode 100644
index 0000000000..61a5446b4c
--- /dev/null
+++ b/modules/stock_supply/view/location_form.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply/view/order_point_form.xml b/modules/stock_supply/view/order_point_form.xml
new file mode 100644
index 0000000000..2cf3c4bb88
--- /dev/null
+++ b/modules/stock_supply/view/order_point_form.xml
@@ -0,0 +1,28 @@
+
+
+
diff --git a/modules/stock_supply/view/order_point_tree.xml b/modules/stock_supply/view/order_point_tree.xml
new file mode 100644
index 0000000000..fc4a7f6e75
--- /dev/null
+++ b/modules/stock_supply/view/order_point_tree.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply/view/purchase_configuration_form.xml b/modules/stock_supply/view/purchase_configuration_form.xml
new file mode 100644
index 0000000000..e190744109
--- /dev/null
+++ b/modules/stock_supply/view/purchase_configuration_form.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply/view/purchase_request_create_start_form.xml b/modules/stock_supply/view/purchase_request_create_start_form.xml
new file mode 100644
index 0000000000..dc3dc3094e
--- /dev/null
+++ b/modules/stock_supply/view/purchase_request_create_start_form.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/modules/stock_supply/view/supply_start_form.xml b/modules/stock_supply/view/supply_start_form.xml
new file mode 100644
index 0000000000..d5d4cf9964
--- /dev/null
+++ b/modules/stock_supply/view/supply_start_form.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/modules/stock_supply_day/.drone.yml b/modules/stock_supply_day/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/stock_supply_day/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/stock_supply_day/.flake8 b/modules/stock_supply_day/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/stock_supply_day/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/stock_supply_day/CHANGELOG b/modules/stock_supply_day/CHANGELOG
new file mode 100644
index 0000000000..b8dfe16d99
--- /dev/null
+++ b/modules/stock_supply_day/CHANGELOG
@@ -0,0 +1,79 @@
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+* Use multiselection for days
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+* Use ir.calendar.day
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.4.0 - 2012-04-24
+* Bug fixes (see mercurial logs for details)
+
+Version 2.2.0 - 2011-10-25
+* Bug fixes (see mercurial logs for details)
+
+Version 2.0.0 - 2011-04-27
+* Bug fixes (see mercurial logs for details)
+
+Version 1.8.0 - 2010-11-01
+* Bug fixes (see mercurial logs for details)
+
+Version 1.6.0 - 2010-05-13
+* Bug fixes (see mercurial logs for details)
+
+Version 1.4.0 - 2009-10-19
+* Bug fixes (see mercurial logs for details)
+
+Version 1.2.0 - 2009-07-31
+* Initial release
diff --git a/modules/stock_supply_day/COPYRIGHT b/modules/stock_supply_day/COPYRIGHT
new file mode 100644
index 0000000000..61f013ab4d
--- /dev/null
+++ b/modules/stock_supply_day/COPYRIGHT
@@ -0,0 +1,16 @@
+Copyright (C) 2008-2021 Cédric Krier.
+Copyright (C) 2008-2012 Bertrand Chenal.
+Copyright (C) 2008-2021 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/stock_supply_day/LICENSE b/modules/stock_supply_day/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/stock_supply_day/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/stock_supply_day/MANIFEST.in b/modules/stock_supply_day/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/stock_supply_day/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/stock_supply_day/README.rst b/modules/stock_supply_day/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/stock_supply_day/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/stock_supply_day/__init__.py b/modules/stock_supply_day/__init__.py
new file mode 100644
index 0000000000..8563c2a257
--- /dev/null
+++ b/modules/stock_supply_day/__init__.py
@@ -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.pool import Pool
+from . import purchase
+
+
+def register():
+ Pool.register(
+ purchase.ProductSupplier,
+ purchase.ProductSupplierDay,
+ module='stock_supply_day', type_='model')
diff --git a/modules/stock_supply_day/doc/index.rst b/modules/stock_supply_day/doc/index.rst
new file mode 100644
index 0000000000..9291e95a0c
--- /dev/null
+++ b/modules/stock_supply_day/doc/index.rst
@@ -0,0 +1,12 @@
+Stock Supply Day Module
+#######################
+
+The Stock Supply Day module adds a Week Days list on the Product
+Supplier form. This allow to restrict the supply week days for each
+supplier on each product. If no days are defined for a supplier a
+supplying may happens at any day of the week.
+
+For example, those week days are taken into account when computing the
+planned date of stock moves created by the purchase (defined in the
+Purchase module) and by the scheduler that generates the Purchase
+Requests (defined in the Stock Supply module).
diff --git a/modules/stock_supply_day/locale/bg.po b/modules/stock_supply_day/locale/bg.po
new file mode 100644
index 0000000000..04aa377a98
--- /dev/null
+++ b/modules/stock_supply_day/locale/bg.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Дни от седмицата"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Доставчик"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Ден на доставчик на наличност"
diff --git a/modules/stock_supply_day/locale/ca.po b/modules/stock_supply_day/locale/ca.po
new file mode 100644
index 0000000000..2ffdd918a9
--- /dev/null
+++ b/modules/stock_supply_day/locale/ca.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Dies de la setmana"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Dia"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Proveïdor"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Dia del proveïdor del producte"
diff --git a/modules/stock_supply_day/locale/cs.po b/modules/stock_supply_day/locale/cs.po
new file mode 100644
index 0000000000..5993b908e0
--- /dev/null
+++ b/modules/stock_supply_day/locale/cs.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr ""
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/de.po b/modules/stock_supply_day/locale/de.po
new file mode 100644
index 0000000000..791b408f3d
--- /dev/null
+++ b/modules/stock_supply_day/locale/de.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Wochentage"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Tag"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Lieferant"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Lieferant Tag"
diff --git a/modules/stock_supply_day/locale/es.po b/modules/stock_supply_day/locale/es.po
new file mode 100644
index 0000000000..211ac97755
--- /dev/null
+++ b/modules/stock_supply_day/locale/es.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Días de la semana"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Día"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Proveedor"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Día del proveedor del producto"
diff --git a/modules/stock_supply_day/locale/es_419.po b/modules/stock_supply_day/locale/es_419.po
new file mode 100644
index 0000000000..5993b908e0
--- /dev/null
+++ b/modules/stock_supply_day/locale/es_419.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr ""
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/et.po b/modules/stock_supply_day/locale/et.po
new file mode 100644
index 0000000000..fe469787f5
--- /dev/null
+++ b/modules/stock_supply_day/locale/et.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Nädalapäevad"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Päev"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Hankija"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Toote hankija päev"
diff --git a/modules/stock_supply_day/locale/fa.po b/modules/stock_supply_day/locale/fa.po
new file mode 100644
index 0000000000..32399efad6
--- /dev/null
+++ b/modules/stock_supply_day/locale/fa.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "روزهای هفته"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "تأمین کننده"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "روز عرضه محصولات"
diff --git a/modules/stock_supply_day/locale/fi.po b/modules/stock_supply_day/locale/fi.po
new file mode 100644
index 0000000000..5993b908e0
--- /dev/null
+++ b/modules/stock_supply_day/locale/fi.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr ""
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/fr.po b/modules/stock_supply_day/locale/fr.po
new file mode 100644
index 0000000000..b23eede9a5
--- /dev/null
+++ b/modules/stock_supply_day/locale/fr.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Jours de la semaine"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Jour"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Fournisseur"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Journée du fournisseur de produit"
diff --git a/modules/stock_supply_day/locale/hu.po b/modules/stock_supply_day/locale/hu.po
new file mode 100644
index 0000000000..f42d142d04
--- /dev/null
+++ b/modules/stock_supply_day/locale/hu.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Hét napjai"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Nap"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Szállító"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Terméket szállító nap"
diff --git a/modules/stock_supply_day/locale/id.po b/modules/stock_supply_day/locale/id.po
new file mode 100644
index 0000000000..02e84da4eb
--- /dev/null
+++ b/modules/stock_supply_day/locale/id.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Hari"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Pemasok"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/it.po b/modules/stock_supply_day/locale/it.po
new file mode 100644
index 0000000000..112eff0efc
--- /dev/null
+++ b/modules/stock_supply_day/locale/it.po
@@ -0,0 +1,20 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Fornitore"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/lo.po b/modules/stock_supply_day/locale/lo.po
new file mode 100644
index 0000000000..6ddaf7f4a5
--- /dev/null
+++ b/modules/stock_supply_day/locale/lo.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "ວັນໃນອາທິດ"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "ຜູ້ສະໜອງ"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "ມື້ສະໜອງຜະລິດຕະພັນ"
diff --git a/modules/stock_supply_day/locale/lt.po b/modules/stock_supply_day/locale/lt.po
new file mode 100644
index 0000000000..5993b908e0
--- /dev/null
+++ b/modules/stock_supply_day/locale/lt.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr ""
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/nl.po b/modules/stock_supply_day/locale/nl.po
new file mode 100644
index 0000000000..68b2e3ef21
--- /dev/null
+++ b/modules/stock_supply_day/locale/nl.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Weekdagen"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr "Dag"
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Leverancier"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Leveranciersdag"
diff --git a/modules/stock_supply_day/locale/pl.po b/modules/stock_supply_day/locale/pl.po
new file mode 100644
index 0000000000..0411e82199
--- /dev/null
+++ b/modules/stock_supply_day/locale/pl.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Dni tygodnia"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Dostawca"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/pt.po b/modules/stock_supply_day/locale/pt.po
new file mode 100644
index 0000000000..d237c84ec2
--- /dev/null
+++ b/modules/stock_supply_day/locale/pt.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Dias da semana"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Fornecedor"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Dia do fornecedor do produto"
diff --git a/modules/stock_supply_day/locale/ro.po b/modules/stock_supply_day/locale/ro.po
new file mode 100644
index 0000000000..5993b908e0
--- /dev/null
+++ b/modules/stock_supply_day/locale/ro.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr ""
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/ru.po b/modules/stock_supply_day/locale/ru.po
new file mode 100644
index 0000000000..2e08107aad
--- /dev/null
+++ b/modules/stock_supply_day/locale/ru.po
@@ -0,0 +1,21 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "День недели"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Поставщик"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/locale/sl.po b/modules/stock_supply_day/locale/sl.po
new file mode 100644
index 0000000000..bb05a78cd6
--- /dev/null
+++ b/modules/stock_supply_day/locale/sl.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr "Dnevi v tednu"
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Dobavitelj"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Dobaviteljev dan"
diff --git a/modules/stock_supply_day/locale/tr.po b/modules/stock_supply_day/locale/tr.po
new file mode 100644
index 0000000000..bf200abf07
--- /dev/null
+++ b/modules/stock_supply_day/locale/tr.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr "Tedarikçi"
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr "Ürün Tedarikçi Günü"
diff --git a/modules/stock_supply_day/locale/zh_CN.po b/modules/stock_supply_day/locale/zh_CN.po
new file mode 100644
index 0000000000..5993b908e0
--- /dev/null
+++ b/modules/stock_supply_day/locale/zh_CN.po
@@ -0,0 +1,19 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:purchase.product_supplier,weekdays:"
+msgid "Week Days"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,day:"
+msgid "Day"
+msgstr ""
+
+msgctxt "field:purchase.product_supplier.day,product_supplier:"
+msgid "Supplier"
+msgstr ""
+
+msgctxt "model:purchase.product_supplier.day,name:"
+msgid "Product Supplier Day"
+msgstr ""
diff --git a/modules/stock_supply_day/purchase.py b/modules/stock_supply_day/purchase.py
new file mode 100644
index 0000000000..a159f57473
--- /dev/null
+++ b/modules/stock_supply_day/purchase.py
@@ -0,0 +1,71 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+from trytond.model import ModelSQL, fields
+from trytond.pool import PoolMeta, Pool
+from trytond.transaction import Transaction
+
+
+class ProductSupplier(metaclass=PoolMeta):
+ __name__ = 'purchase.product_supplier'
+ weekdays = fields.Many2Many(
+ 'purchase.product_supplier.day', 'product_supplier', 'day',
+ "Week Days")
+
+ def compute_supply_date(self, date=None):
+ date = super(ProductSupplier, self).compute_supply_date(date=date)
+ earlier_date = None
+ if date != datetime.date.max:
+ for weekday in self.weekdays:
+ diff = weekday.index - date.weekday()
+ if diff < 0:
+ diff += 7
+ new_date = date + datetime.timedelta(diff)
+
+ if earlier_date and earlier_date <= new_date:
+ continue
+ earlier_date = new_date
+ return earlier_date or date
+
+ def compute_purchase_date(self, date):
+ later_date = None
+ for weekday in self.weekdays:
+ diff = (date.weekday() - weekday.index) % 7
+ new_date = date - datetime.timedelta(diff)
+ if later_date and later_date >= new_date:
+ continue
+ later_date = new_date
+ if later_date:
+ date = later_date
+ return super(ProductSupplier, self).compute_purchase_date(date)
+
+
+class ProductSupplierDay(ModelSQL):
+ 'Product Supplier Day'
+ __name__ = 'purchase.product_supplier.day'
+ product_supplier = fields.Many2One(
+ 'purchase.product_supplier', 'Supplier',
+ required=True, ondelete='CASCADE', select=True)
+ day = fields.Many2One('ir.calendar.day', "Day", required=True)
+
+ @classmethod
+ def __register__(cls, module_name):
+ pool = Pool()
+ Day = pool.get('ir.calendar.day')
+ day = Day.__table__()
+ transaction = Transaction()
+ table = cls.__table__()
+
+ super().__register__(module_name)
+ table_h = cls.__table_handler__(module_name)
+
+ # Migration from 5.0: replace weekday by day
+ if table_h.column_exist('weekday'):
+ cursor = transaction.connection.cursor()
+ update = transaction.connection.cursor()
+ cursor.execute(*day.select(day.id, day.index))
+ for day_id, index in cursor:
+ update.execute(*table.update(
+ [table.day], [day_id],
+ where=table.weekday == str(index)))
+ table_h.drop_column('weekday')
diff --git a/modules/stock_supply_day/purchase.xml b/modules/stock_supply_day/purchase.xml
new file mode 100644
index 0000000000..542be0be3c
--- /dev/null
+++ b/modules/stock_supply_day/purchase.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ purchase.product_supplier
+
+ product_supplier_form
+
+
+
diff --git a/modules/stock_supply_day/setup.py b/modules/stock_supply_day/setup.py
new file mode 100755
index 0000000000..29c48be467
--- /dev/null
+++ b/modules/stock_supply_day/setup.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_stock_supply_day'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to add supply weekdays',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/stock_supply_day',
+ },
+ keywords='tryton supply day',
+ package_dir={'trytond.modules.stock_supply_day': '.'},
+ packages=(
+ ['trytond.modules.stock_supply_day']
+ + ['trytond.modules.stock_supply_day.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.stock_supply_day': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ stock_supply_day = trytond.modules.stock_supply_day
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ )
diff --git a/modules/stock_supply_day/tests/__init__.py b/modules/stock_supply_day/tests/__init__.py
new file mode 100644
index 0000000000..95dae419ff
--- /dev/null
+++ b/modules/stock_supply_day/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.stock_supply_day.tests.test_stock_supply_day import suite # noqa: E501
+except ImportError:
+ from .test_stock_supply_day import suite
+
+__all__ = ['suite']
diff --git a/modules/stock_supply_day/tests/test_stock_supply_day.py b/modules/stock_supply_day/tests/test_stock_supply_day.py
new file mode 100644
index 0000000000..beaecf790b
--- /dev/null
+++ b/modules/stock_supply_day/tests/test_stock_supply_day.py
@@ -0,0 +1,161 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import unittest
+import datetime
+from decimal import Decimal
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.pool import Pool
+
+from trytond.modules.company.tests import (
+ create_company, set_company, CompanyTestMixin)
+from trytond.modules.account.tests import create_chart
+
+
+class StockSupplyDayTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test StockSupplyDay module'
+ module = 'stock_supply_day'
+
+ def test_compute_supply_date(self):
+ 'Test compute_supply_date'
+ dates = [
+ # purchase date, lead time, weekday, supply date
+ (datetime.date(2011, 11, 21), datetime.timedelta(10), 0,
+ datetime.date(2011, 12, 5)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(9), 1,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(8), 2,
+ datetime.date(2011, 11, 30)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(7), 3,
+ datetime.date(2011, 12, 1)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(6), 4,
+ datetime.date(2011, 12, 2)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(5), 5,
+ datetime.date(2011, 11, 26)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(4), 6,
+ datetime.date(2011, 11, 27)),
+ (datetime.date(2011, 12, 22), datetime.timedelta(12), 6,
+ datetime.date(2012, 1, 8)),
+ (datetime.date(2011, 11, 21), datetime.timedelta(10), None,
+ datetime.date(2011, 12, 1)),
+ (datetime.date(2011, 11, 21), None, 0, datetime.date.max),
+ ]
+ # Purchase date is Monday, 2011-11-21, the regular days to deliver is
+ # 10 days, which would be Wednesday 2011-12-01. But with the supplier
+ # weekday 0 (Monday) the forecast supply date is next Monday, the
+ # 2011-12-05.
+
+ @with_transaction()
+ def run(purchase_date, lead_time, weekday, supply_date):
+ product_supplier = self.create_product_supplier_day(
+ lead_time, weekday)
+ date = product_supplier.compute_supply_date(purchase_date)
+ self.assertEqual(date, supply_date)
+ for purchase_date, lead_time, weekday, supply_date in dates:
+ run(purchase_date, lead_time, weekday, supply_date)
+
+ def test_compute_purchase_date(self):
+ 'Test compute_purchase_date'
+ dates = [
+ # purchase date, lead time, weekday, supply date
+ (datetime.date(2011, 11, 25), datetime.timedelta(10), 0,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 27), datetime.timedelta(9), 1,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 22), datetime.timedelta(8), 2,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 24), datetime.timedelta(7), 3,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 26), datetime.timedelta(6), 4,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 28), datetime.timedelta(5), 5,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 11, 30), datetime.timedelta(4), 6,
+ datetime.date(2011, 12, 6)),
+ (datetime.date(2011, 12, 27), datetime.timedelta(6), 0,
+ datetime.date(2012, 1, 3)),
+ ]
+ # Supply date max is Tuesday, 2012-01-03, the supplier weekday 0 which
+ # would be Monday 2012-01-02. But with the 6 days of delivery the
+ # forecast purchase date is the 2011-12-27.
+
+ @with_transaction()
+ def run(purchase_date, lead_time, weekday, supply_date):
+ product_supplier = self.create_product_supplier_day(
+ lead_time, weekday)
+ date = product_supplier.compute_purchase_date(supply_date)
+ self.assertEqual(date, purchase_date)
+ for purchase_date, lead_time, weekday, supply_date in dates:
+ run(purchase_date, lead_time, weekday, supply_date)
+
+ def create_product_supplier_day(self, lead_time, weekday):
+ '''
+ Create a Product with a Product Supplier Day
+
+ :param lead_time: minimal timedelta needed to supply
+ :param weekday: supply day of the week (0 - 6)
+ :return: the id of the Product Supplier Day
+ '''
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ UomCategory = pool.get('product.uom.category')
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ Party = pool.get('party.party')
+ Account = pool.get('account.account')
+ ProductSupplier = pool.get('purchase.product_supplier')
+ ProductSupplierDay = pool.get('purchase.product_supplier.day')
+ Day = pool.get('ir.calendar.day')
+
+ uom_category, = UomCategory.create([{'name': 'Test'}])
+ uom, = Uom.create([{
+ 'name': 'Test',
+ 'symbol': 'T',
+ 'category': uom_category.id,
+ 'rate': 1.0,
+ 'factor': 1.0,
+ }])
+ template, = Template.create([{
+ 'name': 'ProductTest',
+ 'default_uom': uom.id,
+ 'list_price': Decimal(0),
+ }])
+ product, = Product.create([{
+ 'template': template.id,
+ }])
+ company = create_company()
+ with set_company(company):
+ create_chart(company)
+ receivable, = Account.search([
+ ('type.receivable', '=', True),
+ ('company', '=', company.id),
+ ])
+ payable, = Account.search([
+ ('type.payable', '=', True),
+ ('company', '=', company.id),
+ ])
+ supplier, = Party.create([{
+ 'name': 'supplier',
+ 'account_receivable': receivable.id,
+ 'account_payable': payable.id,
+ }])
+ product_supplier, = ProductSupplier.create([{
+ 'template': template.id,
+ 'company': company.id,
+ 'party': supplier.id,
+ 'lead_time': lead_time,
+ }])
+ if weekday is not None:
+ day, = Day.search([('index', '=', weekday)])
+ ProductSupplierDay.create([{
+ 'product_supplier': product_supplier.id,
+ 'day': day.id,
+ }])
+ return product_supplier
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ StockSupplyDayTestCase))
+ return suite
diff --git a/modules/stock_supply_day/tox.ini b/modules/stock_supply_day/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/stock_supply_day/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/stock_supply_day/tryton.cfg b/modules/stock_supply_day/tryton.cfg
new file mode 100644
index 0000000000..7c69d98160
--- /dev/null
+++ b/modules/stock_supply_day/tryton.cfg
@@ -0,0 +1,7 @@
+[tryton]
+version=6.0.1
+depends:
+ ir
+ purchase
+xml:
+ purchase.xml
diff --git a/modules/stock_supply_day/view/product_supplier_form.xml b/modules/stock_supply_day/view/product_supplier_form.xml
new file mode 100644
index 0000000000..111fdc684f
--- /dev/null
+++ b/modules/stock_supply_day/view/product_supplier_form.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply_forecast/.drone.yml b/modules/stock_supply_forecast/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/stock_supply_forecast/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/stock_supply_forecast/.flake8 b/modules/stock_supply_forecast/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/stock_supply_forecast/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/stock_supply_forecast/CHANGELOG b/modules/stock_supply_forecast/CHANGELOG
new file mode 100644
index 0000000000..a16debff03
--- /dev/null
+++ b/modules/stock_supply_forecast/CHANGELOG
@@ -0,0 +1,62 @@
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.4.0 - 2012-04-24
+* Bug fixes (see mercurial logs for details)
+
+Version 2.2.0 - 2011-10-25
+* Initial release
diff --git a/modules/stock_supply_forecast/COPYRIGHT b/modules/stock_supply_forecast/COPYRIGHT
new file mode 100644
index 0000000000..0a72968a7c
--- /dev/null
+++ b/modules/stock_supply_forecast/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2011-2021 Cédric Krier.
+Copyright (C) 2011-2021 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/stock_supply_forecast/LICENSE b/modules/stock_supply_forecast/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/stock_supply_forecast/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/stock_supply_forecast/MANIFEST.in b/modules/stock_supply_forecast/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/stock_supply_forecast/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/stock_supply_forecast/README.rst b/modules/stock_supply_forecast/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/stock_supply_forecast/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/stock_supply_forecast/__init__.py b/modules/stock_supply_forecast/__init__.py
new file mode 100644
index 0000000000..85cf38c4c7
--- /dev/null
+++ b/modules/stock_supply_forecast/__init__.py
@@ -0,0 +1,11 @@
+# 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 purchase_request
+
+
+def register():
+ Pool.register(
+ purchase_request.Request,
+ module='stock_supply_forecast', type_='model')
diff --git a/modules/stock_supply_forecast/doc/index.rst b/modules/stock_supply_forecast/doc/index.rst
new file mode 100644
index 0000000000..eb3b0e20d7
--- /dev/null
+++ b/modules/stock_supply_forecast/doc/index.rst
@@ -0,0 +1,5 @@
+Stock Supply Forecast Module
+############################
+
+The stock supply forecast module takes forecast into account to compute
+purchase requests.
diff --git a/modules/stock_supply_forecast/purchase_request.py b/modules/stock_supply_forecast/purchase_request.py
new file mode 100644
index 0000000000..e7cada7434
--- /dev/null
+++ b/modules/stock_supply_forecast/purchase_request.py
@@ -0,0 +1,23 @@
+# 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 Request(metaclass=PoolMeta):
+ __name__ = 'purchase.request'
+
+ @classmethod
+ def generate_requests(cls, *args, **kwargs):
+ pool = Pool()
+ Forecast = pool.get('stock.forecast')
+ Date = pool.get('ir.date')
+
+ today = Date.today()
+
+ forecasts = Forecast.search([
+ ('to_date', '>=', today),
+ ('state', '=', 'done'),
+ ])
+ Forecast.create_moves(forecasts)
+ super().generate_requests(*args, **kwargs)
+ Forecast.delete_moves(forecasts)
diff --git a/modules/stock_supply_forecast/setup.py b/modules/stock_supply_forecast/setup.py
new file mode 100755
index 0000000000..4ea09195fd
--- /dev/null
+++ b/modules/stock_supply_forecast/setup.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_stock_supply_forecast'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [get_require_version('proteus')]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to add forecast to supply computation',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/stock_supply_forecast',
+ },
+ keywords='tryton stock supply forecast',
+ package_dir={'trytond.modules.stock_supply_forecast': '.'},
+ packages=(
+ ['trytond.modules.stock_supply_forecast']
+ + ['trytond.modules.stock_supply_forecast.%s' % p
+ for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.stock_supply_forecast': (info.get('xml', [])
+ + ['tryton.cfg', 'locale/*.po', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ stock_supply_forecast = trytond.modules.stock_supply_forecast
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/stock_supply_forecast/tests/__init__.py b/modules/stock_supply_forecast/tests/__init__.py
new file mode 100644
index 0000000000..9643ae091f
--- /dev/null
+++ b/modules/stock_supply_forecast/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.stock_supply_forecast.tests.test_stock_supply_forecast import suite # noqa: E501
+except ImportError:
+ from .test_stock_supply_forecast import suite
+
+__all__ = ['suite']
diff --git a/modules/stock_supply_forecast/tests/scenario_stock_supply_forecast.rst b/modules/stock_supply_forecast/tests/scenario_stock_supply_forecast.rst
new file mode 100644
index 0000000000..70022e5778
--- /dev/null
+++ b/modules/stock_supply_forecast/tests/scenario_stock_supply_forecast.rst
@@ -0,0 +1,101 @@
+==============================
+Stock Supply Forecast Scenario
+==============================
+
+Imports::
+
+ >>> import datetime
+ >>> from dateutil.relativedelta import relativedelta
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+ >>> from trytond.modules.account.tests.tools import (create_chart,
+ ... get_accounts)
+ >>> today = datetime.date.today()
+ >>> yesterday = today - relativedelta(days=1)
+ >>> tomorrow = today + relativedelta(days=1)
+
+Activate modules::
+
+ >>> config = activate_modules('stock_supply_forecast')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create chart of accounts::
+
+ >>> _ = create_chart(company)
+ >>> accounts = get_accounts(company)
+ >>> expense = accounts['expense']
+
+Create parties::
+
+ >>> Party = Model.get('party.party')
+ >>> customer = Party(name='Customer')
+ >>> customer.save()
+ >>> supplier = Party(name='Supplier')
+ >>> supplier.save()
+
+Create account category::
+
+ >>> ProductCategory = Model.get('product.category')
+ >>> account_category = ProductCategory(name="Account Category")
+ >>> account_category.accounting = True
+ >>> account_category.account_expense = expense
+ >>> account_category.save()
+
+Create product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'Product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> template.purchasable = True
+ >>> template.account_category = account_category
+ >>> template.save()
+ >>> product, = template.products
+
+Get warehouse location::
+
+ >>> Location = Model.get('stock.location')
+ >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+
+Create a forecast::
+
+ >>> Forecast = Model.get('stock.forecast')
+ >>> forecast = Forecast()
+ >>> forecast.warehouse = warehouse_loc
+ >>> forecast.from_date = yesterday
+ >>> forecast.to_date = tomorrow
+ >>> forecast_line = forecast.lines.new()
+ >>> forecast_line.product = product
+ >>> forecast_line.quantity = 10
+ >>> forecast.save()
+
+There is no purchase request::
+
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+ >>> PurchaseRequest = Model.get('purchase.request')
+ >>> PurchaseRequest.find([])
+ []
+
+There is a draft purchase request after confirming the forecast::
+
+ >>> forecast.click('confirm')
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+ >>> pr, = PurchaseRequest.find([('state', '=', 'draft')])
+ >>> pr.product == product
+ True
+ >>> pr.quantity
+ 10.0
diff --git a/modules/stock_supply_forecast/tests/test_stock_supply_forecast.py b/modules/stock_supply_forecast/tests/test_stock_supply_forecast.py
new file mode 100644
index 0000000000..7b5c7f3c33
--- /dev/null
+++ b/modules/stock_supply_forecast/tests/test_stock_supply_forecast.py
@@ -0,0 +1,24 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import unittest
+import doctest
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase
+from trytond.tests.test_tryton import doctest_teardown
+from trytond.tests.test_tryton import doctest_checker
+
+
+class StockSupplyForecastTestCase(ModuleTestCase):
+ 'Test StockSupplyForecast module'
+ module = 'stock_supply_forecast'
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ StockSupplyForecastTestCase))
+ suite.addTests(doctest.DocFileSuite('scenario_stock_supply_forecast.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ return suite
diff --git a/modules/stock_supply_forecast/tox.ini b/modules/stock_supply_forecast/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/stock_supply_forecast/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/stock_supply_forecast/tryton.cfg b/modules/stock_supply_forecast/tryton.cfg
new file mode 100644
index 0000000000..b5516d8cce
--- /dev/null
+++ b/modules/stock_supply_forecast/tryton.cfg
@@ -0,0 +1,6 @@
+[tryton]
+version=6.0.1
+depends:
+ ir
+ stock_supply
+ stock_forecast
diff --git a/modules/stock_supply_production/.drone.yml b/modules/stock_supply_production/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/stock_supply_production/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/stock_supply_production/.flake8 b/modules/stock_supply_production/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/stock_supply_production/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/stock_supply_production/CHANGELOG b/modules/stock_supply_production/CHANGELOG
new file mode 100644
index 0000000000..3f982a68b7
--- /dev/null
+++ b/modules/stock_supply_production/CHANGELOG
@@ -0,0 +1,67 @@
+Version 6.0.1 - 2021-07-21
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+* Merge supply into one wizard
+* Do not create production request if other order point exists
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+* Add warning for late production when creating production request.
+
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+* Allow to calculate production requests filtered by warehouse
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.4.0 - 2012-08-15
+* Initial release
diff --git a/modules/stock_supply_production/COPYRIGHT b/modules/stock_supply_production/COPYRIGHT
new file mode 100644
index 0000000000..4acb8540a3
--- /dev/null
+++ b/modules/stock_supply_production/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2012-2021 Cédric Krier.
+Copyright (C) 2012-2021 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/stock_supply_production/LICENSE b/modules/stock_supply_production/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/stock_supply_production/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/stock_supply_production/MANIFEST.in b/modules/stock_supply_production/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/stock_supply_production/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/stock_supply_production/README.rst b/modules/stock_supply_production/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/stock_supply_production/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/stock_supply_production/__init__.py b/modules/stock_supply_production/__init__.py
new file mode 100644
index 0000000000..e356141069
--- /dev/null
+++ b/modules/stock_supply_production/__init__.py
@@ -0,0 +1,21 @@
+# 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 stock
+from . import production
+from . import product
+
+
+def register():
+ Pool.register(
+ stock.OrderPoint,
+ stock.LocationLeadTime,
+ production.Configuration,
+ production.ConfigurationSupplyPeriod,
+ production.Production,
+ product.Product,
+ module='stock_supply_production', type_='model')
+ Pool.register(
+ stock.StockSupply,
+ module='stock_supply_production', type_='wizard')
diff --git a/modules/stock_supply_production/doc/index.rst b/modules/stock_supply_production/doc/index.rst
new file mode 100644
index 0000000000..c09c4954b1
--- /dev/null
+++ b/modules/stock_supply_production/doc/index.rst
@@ -0,0 +1,27 @@
+Stock Supply Production Module
+##############################
+
+The Stock Supply Production module adds automatic supply mechanisms via
+production request.
+
+Production Request
+******************
+
+A production request is a production in the state request. It defines the need
+to produce a product.
+
+
+Order Point
+***********
+
+It adds a new type of Order Point:
+
+* Production
+
+ A Production order point is defined on a warehouse location. If the minimal
+ quantity is reached at any time on the warehouse it will result in a
+ production request.
+
+The production requests are created by the supply wizard with respect to stock
+levels and existing requests. The stock levels are computed on the supply
+period define in the production configuration.
diff --git a/modules/stock_supply_production/locale/bg.po b/modules/stock_supply_production/locale/bg.po
new file mode 100644
index 0000000000..9302d95332
--- /dev/null
+++ b/modules/stock_supply_production/locale/bg.po
@@ -0,0 +1,34 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Управление на производство"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Управление на производство"
diff --git a/modules/stock_supply_production/locale/ca.po b/modules/stock_supply_production/locale/ca.po
new file mode 100644
index 0000000000..27fd06dfe6
--- /dev/null
+++ b/modules/stock_supply_production/locale/ca.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Període de subministre"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Període de subministre"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Sol·licituds de producció"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Producció"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Hi ha algunes produccions endarrerides."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Configuració del període de subministre de la producció"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Producció"
diff --git a/modules/stock_supply_production/locale/cs.po b/modules/stock_supply_production/locale/cs.po
new file mode 100644
index 0000000000..f6ad374dc8
--- /dev/null
+++ b/modules/stock_supply_production/locale/cs.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/locale/de.po b/modules/stock_supply_production/locale/de.po
new file mode 100644
index 0000000000..42f415213f
--- /dev/null
+++ b/modules/stock_supply_production/locale/de.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Beschaffungszeitraum"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Beschaffungszeitraum"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Produktionsaufträge anfordern"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Produktion"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Einige Produktionsaufträge sind verspätet."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Einstellungen Produktion Beschaffungszeitraum"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Produktion"
diff --git a/modules/stock_supply_production/locale/es.po b/modules/stock_supply_production/locale/es.po
new file mode 100644
index 0000000000..dcef7b35ce
--- /dev/null
+++ b/modules/stock_supply_production/locale/es.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Período de suministro"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Período de suministro"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Solicitudes de producción"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Producción"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Hay algunas producciones retrasadas."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Configuración del periodo de subministro de la producción"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Producción"
diff --git a/modules/stock_supply_production/locale/es_419.po b/modules/stock_supply_production/locale/es_419.po
new file mode 100644
index 0000000000..89090c62b2
--- /dev/null
+++ b/modules/stock_supply_production/locale/es_419.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr ""
diff --git a/modules/stock_supply_production/locale/et.po b/modules/stock_supply_production/locale/et.po
new file mode 100644
index 0000000000..3571c40292
--- /dev/null
+++ b/modules/stock_supply_production/locale/et.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Tarneaeg"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Tarneaeg"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Tootmiste päring"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Tootmine"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Osad tooted hilinevad."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Tootmise seadistus tarneaeg"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Tootmine"
diff --git a/modules/stock_supply_production/locale/fa.po b/modules/stock_supply_production/locale/fa.po
new file mode 100644
index 0000000000..9444459661
--- /dev/null
+++ b/modules/stock_supply_production/locale/fa.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "دوره عرضه"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "دوره عرضه"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "درخواست تولیدات"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "تولید"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "برخی از محصولات تأخیر دارند."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "پیکربندی تولید ؛ دوره عرضه"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "تولید"
diff --git a/modules/stock_supply_production/locale/fi.po b/modules/stock_supply_production/locale/fi.po
new file mode 100644
index 0000000000..f6ad374dc8
--- /dev/null
+++ b/modules/stock_supply_production/locale/fi.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/locale/fr.po b/modules/stock_supply_production/locale/fr.po
new file mode 100644
index 0000000000..dcb6503012
--- /dev/null
+++ b/modules/stock_supply_production/locale/fr.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Période d'approvisionnement"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Période d'approvisionnement"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Demandes de production"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Il y a des productions qui sont en retard."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Configuration production période d'approvisionnement"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/locale/hu.po b/modules/stock_supply_production/locale/hu.po
new file mode 100644
index 0000000000..d5d5e29d4b
--- /dev/null
+++ b/modules/stock_supply_production/locale/hu.po
@@ -0,0 +1,35 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Beszerzési időszak"
+
+#, fuzzy
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Beszerzési időszak"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Termelés"
diff --git a/modules/stock_supply_production/locale/id.po b/modules/stock_supply_production/locale/id.po
new file mode 100644
index 0000000000..245e22600a
--- /dev/null
+++ b/modules/stock_supply_production/locale/id.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Produksi"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Ada beberapa produksi yang terlambat."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Periode Pasokan Konfigurasi Produksi"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Produksi"
diff --git a/modules/stock_supply_production/locale/it.po b/modules/stock_supply_production/locale/it.po
new file mode 100644
index 0000000000..f6ad374dc8
--- /dev/null
+++ b/modules/stock_supply_production/locale/it.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/locale/lo.po b/modules/stock_supply_production/locale/lo.po
new file mode 100644
index 0000000000..3acdd87707
--- /dev/null
+++ b/modules/stock_supply_production/locale/lo.po
@@ -0,0 +1,34 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "ໄລຍະສະໜອງ"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "ໄລຍະສະໜອງ"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "ໄລຍະກຳນົດການສະໜອງຜະລິດຕະພັນ"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "ການຜະລິດ"
diff --git a/modules/stock_supply_production/locale/lt.po b/modules/stock_supply_production/locale/lt.po
new file mode 100644
index 0000000000..f6ad374dc8
--- /dev/null
+++ b/modules/stock_supply_production/locale/lt.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/locale/nl.po b/modules/stock_supply_production/locale/nl.po
new file mode 100644
index 0000000000..bdf94715ec
--- /dev/null
+++ b/modules/stock_supply_production/locale/nl.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Leveringstermijn"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Leveringstermijn"
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Vraag productie aan"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Productie"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr "Er zijn enkele producties die te laat zijn."
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Productieconfiguratie Leveringsperiode"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Productie"
diff --git a/modules/stock_supply_production/locale/pl.po b/modules/stock_supply_production/locale/pl.po
new file mode 100644
index 0000000000..dfab04d2f5
--- /dev/null
+++ b/modules/stock_supply_production/locale/pl.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Produkcja"
diff --git a/modules/stock_supply_production/locale/pt.po b/modules/stock_supply_production/locale/pt.po
new file mode 100644
index 0000000000..0406e755bf
--- /dev/null
+++ b/modules/stock_supply_production/locale/pt.po
@@ -0,0 +1,34 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Período de Abastecimento"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Período de Abastecimento"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Configurações de Produção Período de Abastecimento"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Produção"
diff --git a/modules/stock_supply_production/locale/ro.po b/modules/stock_supply_production/locale/ro.po
new file mode 100644
index 0000000000..89090c62b2
--- /dev/null
+++ b/modules/stock_supply_production/locale/ro.po
@@ -0,0 +1,32 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr ""
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr ""
diff --git a/modules/stock_supply_production/locale/ru.po b/modules/stock_supply_production/locale/ru.po
new file mode 100644
index 0000000000..a716b697aa
--- /dev/null
+++ b/modules/stock_supply_production/locale/ru.po
@@ -0,0 +1,34 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Производство"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Производство"
diff --git a/modules/stock_supply_production/locale/sl.po b/modules/stock_supply_production/locale/sl.po
new file mode 100644
index 0000000000..c661417a63
--- /dev/null
+++ b/modules/stock_supply_production/locale/sl.po
@@ -0,0 +1,34 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr "Obdobje dobave"
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr "Obdobje dobave"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+#, fuzzy
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr "Konfiguracija obdobja dobave proizvodnje"
+
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Proizvodni nalog"
diff --git a/modules/stock_supply_production/locale/tr.po b/modules/stock_supply_production/locale/tr.po
new file mode 100644
index 0000000000..f6ad374dc8
--- /dev/null
+++ b/modules/stock_supply_production/locale/tr.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/locale/zh_CN.po b/modules/stock_supply_production/locale/zh_CN.po
new file mode 100644
index 0000000000..f6ad374dc8
--- /dev/null
+++ b/modules/stock_supply_production/locale/zh_CN.po
@@ -0,0 +1,33 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:production.configuration,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "field:production.configuration.supply_period,supply_period:"
+msgid "Supply Period"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_production_request"
+msgid "Request Productions"
+msgstr "Request Productions"
+
+msgctxt ""
+"model:ir.action.act_window.domain,name:act_order_point_form_domain_production"
+msgid "Production"
+msgstr "Production"
+
+msgctxt "model:ir.message,text:msg_late_productions"
+msgid "There are some productions that are late."
+msgstr ""
+
+msgctxt "model:production.configuration.supply_period,name:"
+msgid "Production Configuration Supply Period"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:stock.order_point,type:"
+msgid "Production"
+msgstr "Production"
diff --git a/modules/stock_supply_production/message.xml b/modules/stock_supply_production/message.xml
new file mode 100644
index 0000000000..d934b5eb3f
--- /dev/null
+++ b/modules/stock_supply_production/message.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ There are some productions that are late.
+
+
+
diff --git a/modules/stock_supply_production/product.py b/modules/stock_supply_production/product.py
new file mode 100644
index 0000000000..71706f7da5
--- /dev/null
+++ b/modules/stock_supply_production/product.py
@@ -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 datetime import timedelta
+
+from trytond.pool import Pool, PoolMeta
+
+
+class Product(metaclass=PoolMeta):
+ __name__ = 'product.product'
+
+ def get_supply_period(self):
+ 'Return the supply period for the product'
+ pool = Pool()
+ Configuration = pool.get('production.configuration')
+ return Configuration(1).supply_period or timedelta(0)
diff --git a/modules/stock_supply_production/production.py b/modules/stock_supply_production/production.py
new file mode 100644
index 0000000000..80881933ca
--- /dev/null
+++ b/modules/stock_supply_production/production.py
@@ -0,0 +1,235 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+from collections import defaultdict
+
+from trytond.model import ModelSQL, ValueMixin, fields
+from trytond.pool import Pool, PoolMeta
+from trytond.transaction import Transaction
+from trytond.tools import grouped_slice
+
+supply_period = fields.TimeDelta("Supply Period")
+
+
+class Configuration(metaclass=PoolMeta):
+ __name__ = 'production.configuration'
+ supply_period = fields.MultiValue(supply_period)
+
+
+class ConfigurationSupplyPeriod(ModelSQL, ValueMixin):
+ "Production Configuration Supply Period"
+ __name__ = 'production.configuration.supply_period'
+ supply_period = supply_period
+
+
+class Production(metaclass=PoolMeta):
+ __name__ = 'production'
+
+ @classmethod
+ def _get_origin(cls):
+ origins = super()._get_origin()
+ return origins | {'stock.order_point'}
+
+ @classmethod
+ def generate_requests(cls, clean=True, warehouses=None):
+ """
+ For each product compute the production request that must be created
+ today to meet product outputs.
+
+ If clean is set, it will remove all previous requests.
+
+ If warehouses is specified it will compute the production requests
+ only for the selected warehouses.
+ """
+ pool = Pool()
+ OrderPoint = pool.get('stock.order_point')
+ Product = pool.get('product.product')
+ Location = pool.get('stock.location')
+ Date = pool.get('ir.date')
+ User = pool.get('res.user')
+ company = User(Transaction().user).company
+
+ if clean:
+ reqs = cls.search([
+ ('state', '=', 'request'),
+ ('origin', 'like', 'stock.order_point,%'),
+ ])
+ if warehouses:
+ reqs = [r for r in reqs if r.warehouse in warehouses]
+ cls.delete(reqs)
+
+ if warehouses is None:
+ # fetch warehouse
+ warehouses = Location.search([
+ ('type', '=', 'warehouse'),
+ ])
+ warehouse_ids = [w.id for w in warehouses]
+ # fetch order points
+ order_points = OrderPoint.search([
+ ('warehouse_location', '!=', None),
+ ('company', '=', company.id if company else None),
+ ])
+ # index them by product
+ product2ops = {}
+ product2ops_other = {}
+ for order_point in order_points:
+ if order_point.type == 'production':
+ dict_ = product2ops
+ else:
+ dict_ = product2ops_other
+ dict_[
+ (order_point.warehouse_location.id, order_point.product.id)
+ ] = order_point
+
+ # fetch goods
+ products = Product.search([
+ ('type', '=', 'goods'),
+ ('consumable', '=', False),
+ ('producible', '=', True),
+ ])
+ # compute requests
+ today = Date.today()
+ requests = []
+ for sub_products in grouped_slice(products):
+ sub_products = list(sub_products)
+ product_ids = [p.id for p in sub_products]
+ with Transaction().set_context(forecast=True,
+ stock_date_end=today):
+ pbl = Product.products_by_location(
+ warehouse_ids,
+ with_childs=True,
+ grouping_filter=(product_ids,))
+
+ # order product by supply period
+ products_period = sorted((p.get_supply_period(), p)
+ for p in sub_products)
+
+ for warehouse in warehouses:
+ quantities = defaultdict(int,
+ ((x, pbl.pop((warehouse.id, x), 0)) for x in product_ids))
+ # Do not compute shortage for product
+ # with different order point
+ product_ids = [
+ p.id for p in sub_products
+ if (warehouse.id, p.id) not in product2ops_other]
+ shortages = cls.get_shortage(warehouse.id, product_ids, today,
+ quantities, products_period, product2ops)
+
+ for product in sub_products:
+ if product.id not in shortages:
+ continue
+ for date, quantity in shortages[product.id]:
+ order_point = product2ops.get(
+ (warehouse.id, product.id))
+ req = cls.compute_request(product, warehouse,
+ quantity, date, company, order_point)
+ req.planned_start_date = (
+ req.on_change_with_planned_start_date())
+ requests.append(req)
+ cls.save(requests)
+ cls.set_moves(requests)
+ return requests
+
+ @classmethod
+ def compute_request(
+ cls, product, warehouse, quantity, date, company,
+ order_point=None):
+ """
+ Return the value of the production request.
+ """
+ pool = Pool()
+ Date = pool.get('ir.date')
+ today = Date.today()
+ if date <= today:
+ date = today
+ else:
+ date -= datetime.timedelta(1)
+ uom = product.default_uom
+ quantity = uom.ceil(quantity)
+ if order_point:
+ origin = str(order_point)
+ else:
+ origin = 'stock.order_point,-1'
+ return cls(
+ planned_date=date,
+ company=company,
+ warehouse=warehouse,
+ location=warehouse.production_location,
+ product=product,
+ bom=product.boms[0].bom if product.boms else None,
+ uom=uom,
+ quantity=quantity,
+ state='request',
+ origin=origin,
+ )
+
+ @classmethod
+ def get_shortage(cls, location_id, product_ids, date, quantities,
+ products_period, order_points):
+ """
+ Return for each product a list of dates where the stock quantity is
+ less than the minimal quantity and the quantity to reach the maximal
+ quantity over the period.
+
+ The minimal and maximal quantities come from the order point or are
+ zero.
+
+ quantities is the quantities for each product at the date.
+ products_period is an ordered list of periods and products.
+ order_points is a dictionary that links products to order points.
+ """
+ pool = Pool()
+ Product = pool.get('product.product')
+
+ shortages = {}
+
+ min_quantities = {}
+ target_quantities = {}
+ for product_id in product_ids:
+ order_point = order_points.get((location_id, product_id))
+ if order_point:
+ min_quantities[product_id] = order_point.min_quantity
+ target_quantities[product_id] = order_point.target_quantity
+ else:
+ min_quantities[product_id] = 0.0
+ target_quantities[product_id] = 0.0
+ shortages[product_id] = []
+
+ products_period = products_period[:]
+ current_date = date
+ current_qties = quantities.copy()
+ product_ids = product_ids[:]
+ while product_ids:
+ for product_id in product_ids:
+ current_qty = current_qties[product_id]
+ min_quantity = min_quantities[product_id]
+ if min_quantity is not None and current_qty < min_quantity:
+ target_quantity = target_quantities[product_id]
+ quantity = target_quantity - current_qty
+ shortages[product_id].append((current_date, quantity))
+ current_qties[product_id] += quantity
+
+ # Remove product with smaller period
+ while (products_period
+ and products_period[0][0] <= (current_date - date)):
+ _, product = products_period.pop(0)
+ try:
+ product_ids.remove(product.id)
+ except ValueError:
+ # product may have been already removed on get_shortages
+ pass
+ current_date += datetime.timedelta(1)
+
+ # Update current quantities with next moves
+ with Transaction().set_context(forecast=True,
+ stock_date_start=current_date,
+ stock_date_end=current_date):
+ pbl = Product.products_by_location(
+ [location_id],
+ with_childs=True,
+ grouping_filter=(product_ids,))
+ for key, qty in pbl.items():
+ _, product_id = key
+ current_qties[product_id] += qty
+
+ return shortages
diff --git a/modules/stock_supply_production/production.xml b/modules/stock_supply_production/production.xml
new file mode 100644
index 0000000000..381ea47765
--- /dev/null
+++ b/modules/stock_supply_production/production.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ production.configuration
+
+ production_configuration_form
+
+
+
+ Request Productions
+ production
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/stock_supply_production/setup.py b/modules/stock_supply_production/setup.py
new file mode 100755
index 0000000000..c8103a7c92
--- /dev/null
+++ b/modules/stock_supply_production/setup.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_stock_supply_production'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [get_require_version('proteus')]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module for stock supply of production',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/stock_supply_production',
+ },
+ keywords='tryton stock supply production',
+ package_dir={'trytond.modules.stock_supply_production': '.'},
+ packages=(
+ ['trytond.modules.stock_supply_production']
+ + ['trytond.modules.stock_supply_production.%s' % p
+ for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.stock_supply_production': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ stock_supply_production = trytond.modules.stock_supply_production
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/stock_supply_production/stock.py b/modules/stock_supply_production/stock.py
new file mode 100644
index 0000000000..b7bc045d42
--- /dev/null
+++ b/modules/stock_supply_production/stock.py
@@ -0,0 +1,107 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+
+from trytond.i18n import gettext
+from trytond.pyson import Eval, If
+from trytond.pool import PoolMeta, Pool
+from trytond.wizard import StateAction
+
+from trytond.modules.stock_supply.exceptions import SupplyWarning
+
+
+class OrderPoint(metaclass=PoolMeta):
+ __name__ = 'stock.order_point'
+
+ @classmethod
+ def __setup__(cls):
+ super(OrderPoint, cls).__setup__()
+
+ cls.product.domain = [
+ cls.product.domain,
+ If(Eval('type') == 'production',
+ ('producible', '=', True),
+ ()),
+ ]
+ if 'type' not in cls.product.depends:
+ cls.product.depends.append('type')
+
+ cls.warehouse_location.states['invisible'] &= (
+ Eval('type') != 'production')
+ cls.warehouse_location.states['required'] |= (
+ Eval('type') == 'production')
+
+ option = ('production', 'Production')
+ if option not in cls.type.selection:
+ cls.type.selection.append(option)
+
+ @classmethod
+ def _type2field(cls, type=None):
+ if type == 'production':
+ return 'warehouse_location'
+ result = super(OrderPoint, cls)._type2field(type=type)
+ if type is None:
+ result['production'] = 'warehouse_location'
+ return result
+
+ def get_location(self, name):
+ location = super(OrderPoint, self).get_location(name)
+ if self.type == 'production':
+ return self.warehouse_location.id
+ return location
+
+
+class LocationLeadTime(metaclass=PoolMeta):
+ __name__ = 'stock.location.lead_time'
+
+ @classmethod
+ def _get_extra_lead_times(cls):
+ pool = Pool()
+ Configuration = pool.get('production.configuration')
+ config = Configuration(1)
+ supply_period = config.get_multivalue('supply_period')
+ extra = super(LocationLeadTime, cls)._get_extra_lead_times()
+ extra.append(supply_period or datetime.timedelta(0))
+ return extra
+
+
+class StockSupply(metaclass=PoolMeta):
+ __name__ = 'stock.supply'
+
+ production = StateAction('stock_supply_production.act_production_request')
+
+ @classmethod
+ def types(cls):
+ return super(StockSupply, cls).types() + ['production']
+
+ def transition_create_(self):
+ pool = Pool()
+ Date = pool.get('ir.date')
+ Move = pool.get('stock.move')
+ Warning = pool.get('res.user.warning')
+ today = Date.today()
+ if Move.search([
+ ('from_location.type', '=', 'production'),
+ ('to_location.type', '=', 'storage'),
+ ('state', '=', 'draft'),
+ ('planned_date', '<', today),
+ ], order=[]):
+ key = '%s@%s' % (self.__name__, today)
+ if Warning.check(key):
+ raise SupplyWarning(
+ key,
+ gettext('stock_supply_production.msg_late_productions'))
+ return super(StockSupply, self).transition_create_()
+
+ @property
+ def _production_parameters(self):
+ return {}
+
+ def generate_production(self, clean):
+ pool = Pool()
+ Production = pool.get('production')
+ return Production.generate_requests(
+ clean=clean, **self._production_parameters)
+
+ def transition_production(self):
+ return self.next_action('production')
diff --git a/modules/stock_supply_production/stock.xml b/modules/stock_supply_production/stock.xml
new file mode 100644
index 0000000000..60ff5b7355
--- /dev/null
+++ b/modules/stock_supply_production/stock.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Production
+
+
+
+
+
+
diff --git a/modules/stock_supply_production/tests/__init__.py b/modules/stock_supply_production/tests/__init__.py
new file mode 100644
index 0000000000..01f746ff4e
--- /dev/null
+++ b/modules/stock_supply_production/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.stock_supply_production.tests.test_stock_supply_production import suite # noqa: E501
+except ImportError:
+ from .test_stock_supply_production import suite
+
+__all__ = ['suite']
diff --git a/modules/stock_supply_production/tests/scenario_stock_supply_production.rst b/modules/stock_supply_production/tests/scenario_stock_supply_production.rst
new file mode 100644
index 0000000000..3f8c828532
--- /dev/null
+++ b/modules/stock_supply_production/tests/scenario_stock_supply_production.rst
@@ -0,0 +1,139 @@
+===========================
+Production Request Scenario
+===========================
+
+Imports::
+
+ >>> from datetime import timedelta
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+
+Activate modules::
+
+ >>> config = activate_modules('stock_supply_production')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+ >>> ProductTemplate = Model.get('product.template')
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.producible = True
+ >>> template.list_price = Decimal(30)
+ >>> template.save()
+ >>> product, = template.products
+
+Define a supply period for production::
+
+ >>> ProductionConfiguration = Model.get('production.configuration')
+ >>> production_configuration = ProductionConfiguration(1)
+ >>> production_configuration.supply_period = timedelta(1)
+ >>> production_configuration.save()
+
+Get stock locations::
+
+ >>> Location = Model.get('stock.location')
+ >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+ >>> storage_loc, = Location.find([('code', '=', 'STO')])
+ >>> lost_loc, = Location.find([('type', '=', 'lost_found')])
+
+Create a need for product::
+
+ >>> Move = Model.get('stock.move')
+ >>> move = Move()
+ >>> move.product = product
+ >>> move.quantity = 1
+ >>> move.from_location = storage_loc
+ >>> move.to_location = lost_loc
+ >>> move.click('do')
+ >>> move.state
+ 'done'
+
+The is no production request::
+
+ >>> Production = Model.get('production')
+ >>> Production.find([])
+ []
+
+Create production request::
+
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+There is now a production request::
+
+ >>> production, = Production.find([])
+ >>> production.state
+ 'request'
+ >>> production.product == product
+ True
+ >>> production.quantity
+ 1.0
+
+Create an order point negative minimal quantity::
+
+ >>> OrderPoint = Model.get('stock.order_point')
+ >>> order_point = OrderPoint()
+ >>> order_point.type = 'production'
+ >>> order_point.product = product
+ >>> order_point.warehouse_location = warehouse_loc
+ >>> order_point.min_quantity = -1
+ >>> order_point.target_quantity = 10
+ >>> order_point.save()
+
+Create production request::
+
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+The is no more production request::
+
+ >>> Production = Model.get('production')
+ >>> Production.find([])
+ []
+
+Set a minimal quantity on order point::
+
+ >>> order_point.min_quantity = 5
+ >>> order_point.save()
+
+Create production request::
+
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+
+There is now a production request::
+
+ >>> production, = Production.find([])
+ >>> production.state
+ 'request'
+ >>> production.product == product
+ True
+ >>> production.quantity
+ 11.0
+
+Using zero as minimal quantity also creates a production request::
+
+ >>> order_point.min_quantity = 0
+ >>> order_point.save()
+ >>> create_pr = Wizard('stock.supply')
+ >>> create_pr.execute('create_')
+ >>> production, = Production.find([])
+ >>> production.state
+ 'request'
+ >>> production.product == product
+ True
+ >>> production.quantity
+ 11.0
diff --git a/modules/stock_supply_production/tests/test_stock_supply_production.py b/modules/stock_supply_production/tests/test_stock_supply_production.py
new file mode 100644
index 0000000000..ee63766bbb
--- /dev/null
+++ b/modules/stock_supply_production/tests/test_stock_supply_production.py
@@ -0,0 +1,25 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import unittest
+import doctest
+
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase
+from trytond.tests.test_tryton import doctest_teardown, doctest_checker
+
+
+class StockSupplyProductionTestCase(ModuleTestCase):
+ 'Test Stock Supply Production module'
+ module = 'stock_supply_production'
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ StockSupplyProductionTestCase))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_stock_supply_production.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ return suite
diff --git a/modules/stock_supply_production/tox.ini b/modules/stock_supply_production/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/stock_supply_production/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/stock_supply_production/tryton.cfg b/modules/stock_supply_production/tryton.cfg
new file mode 100644
index 0000000000..ba7fb94960
--- /dev/null
+++ b/modules/stock_supply_production/tryton.cfg
@@ -0,0 +1,12 @@
+[tryton]
+version=6.0.2
+depends:
+ ir
+ product
+ production
+ stock
+ stock_supply
+xml:
+ production.xml
+ stock.xml
+ message.xml
diff --git a/modules/stock_supply_production/view/production_configuration_form.xml b/modules/stock_supply_production/view/production_configuration_form.xml
new file mode 100644
index 0000000000..59b6eed387
--- /dev/null
+++ b/modules/stock_supply_production/view/production_configuration_form.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/.drone.yml b/modules/timesheet/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/timesheet/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/timesheet/.flake8 b/modules/timesheet/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/timesheet/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/timesheet/CHANGELOG b/modules/timesheet/CHANGELOG
new file mode 100644
index 0000000000..f1dc7542f8
--- /dev/null
+++ b/modules/timesheet/CHANGELOG
@@ -0,0 +1,95 @@
+Version 6.0.2 - 2022-10-17
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.1 - 2022-04-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+* Use window domain to show "open" and "closed" timesheet works
+* Remove active from timesheet work
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+* Use ir.calendar.month in report
+* Add date to window name when entering timesheet
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Add UUID on line for synchronisation
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+* Add read access to any line for all
+* Add domain on start/end date of work
+* Add user application API
+* Remove tree structure on Work
+* Add optional origin on Work
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+* Split duration into simple and total duration
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+* Use TimeDelta field
+
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+* Add start/end date to work for timesheet
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.4.0 - 2012-04-24
+* Bug fixes (see mercurial logs for details)
+* Change record rule for work to use the current company instead of main
+
+Version 2.2.0 - 2011-10-25
+* Bug fixes (see mercurial logs for details)
+
+Version 2.0.0 - 2011-04-27
+* Bug fixes (see mercurial logs for details)
+
+Version 1.8.0 - 2010-11-01
+* Bug fixes (see mercurial logs for details)
+
+Version 1.6.0 - 2010-05-12
+* Bug fixes (see mercurial logs for details)
+
+Version 1.4.0 - 2009-10-19
+* Initial release
diff --git a/modules/timesheet/COPYRIGHT b/modules/timesheet/COPYRIGHT
new file mode 100644
index 0000000000..85e100bbec
--- /dev/null
+++ b/modules/timesheet/COPYRIGHT
@@ -0,0 +1,16 @@
+Copyright (C) 2008-2022 Cédric Krier.
+Copyright (C) 2008-2013 Bertrand Chenal.
+Copyright (C) 2008-2022 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/timesheet/LICENSE b/modules/timesheet/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/timesheet/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/timesheet/MANIFEST.in b/modules/timesheet/MANIFEST.in
new file mode 100644
index 0000000000..94499680c6
--- /dev/null
+++ b/modules/timesheet/MANIFEST.in
@@ -0,0 +1,6 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
+include icons/LICENSE
diff --git a/modules/timesheet/README.rst b/modules/timesheet/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/timesheet/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/timesheet/__init__.py b/modules/timesheet/__init__.py
new file mode 100644
index 0000000000..0e7399924e
--- /dev/null
+++ b/modules/timesheet/__init__.py
@@ -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
+
+from . import work
+from . import line
+from . import user
+from . import routes
+
+__all__ = ['register', 'routes']
+
+
+def register():
+ Pool.register(
+ work.Work,
+ work.WorkContext,
+ line.Line,
+ line.EnterLinesStart,
+ line.HoursEmployee,
+ line.HoursEmployeeContext,
+ line.HoursEmployeeWeekly,
+ line.HoursEmployeeMonthly,
+ user.UserApplication,
+ module='timesheet', type_='model')
+ Pool.register(
+ line.EnterLines,
+ module='timesheet', type_='wizard')
diff --git a/modules/timesheet/doc/index.rst b/modules/timesheet/doc/index.rst
new file mode 100644
index 0000000000..eab62177f0
--- /dev/null
+++ b/modules/timesheet/doc/index.rst
@@ -0,0 +1,32 @@
+Timesheet Module
+################
+
+The timesheet module allow to track the time spent by employees on
+various works. This module also comes with several reports that show
+the time spent by employees on works following various time periods.
+
+
+Work
+****
+
+A work is a generic concept that encompass all activities from simple
+tasks to long-running projects. It can be alone or linked to another document.
+The Work model contains the following fields:
+
+- Name or Origin: The name or the origin of the work.
+- Active: A checkbox that allow to disable the work.
+- Company: The company for which the work is (or was) executed.
+
+
+Timesheet Line
+**************
+
+A timesheet line expresses the fact that one employee spends part of
+their time doing specific work on a given date. An optional
+Description field allows extra information to be recorded about what
+has been done.
+
+.. toctree::
+ :maxdepth: 1
+
+ user_application
diff --git a/modules/timesheet/doc/user_application.rst b/modules/timesheet/doc/user_application.rst
new file mode 100644
index 0000000000..9a3dc9f938
--- /dev/null
+++ b/modules/timesheet/doc/user_application.rst
@@ -0,0 +1,30 @@
+Timesheet User Application
+##########################
+
+The timesheet module defines some routes for user applications:
+
+ - `GET` `//timesheet/employees`:
+ return the list of the user employees as dictionary with the keys: `id`
+ and `name`.
+
+ - `GET` `//timesheet/employee//works`:
+ return the list of works available for the employee. The works are
+ dictionary with keys: `id`, `name`, `start` and `end`. The dates are in
+ the format `%Y-%m-%d`.
+
+ - `GET` `//timesheet/employee//lines/`:
+ return the list of timesheet lines for the employee at the date
+ (`%Y-%m-%d'). The lines are dictionary with keys: `id`, `work`,
+ `work.name`, `duration`, `description` and `uuid`. The duration is in
+ seconds.
+
+ - `POST` `//timesheet/line`:
+ Create a line using the data as dictionary of value. The date must be in
+ the format `%Y-%m-%d` and the `duration` in seconds. If a `uuid` value is
+ provided, it will update the record if found instead of create a new one.
+
+ - `PUT` `//timesheet/line/`:
+ Update the line using the data like for the `POST`.
+
+ - `DELETE` `//timesheet/line/`:
+ Delete the line
diff --git a/modules/timesheet/exceptions.py b/modules/timesheet/exceptions.py
new file mode 100644
index 0000000000..ef503d5730
--- /dev/null
+++ b/modules/timesheet/exceptions.py
@@ -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.model.exceptions import ValidationError
+
+
+class CompanyValidationError(ValidationError):
+ pass
+
+
+class DurationValidationError(ValidationError):
+ pass
diff --git a/modules/timesheet/icons/LICENSE b/modules/timesheet/icons/LICENSE
new file mode 100644
index 0000000000..7a4a3ea242
--- /dev/null
+++ b/modules/timesheet/icons/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/modules/timesheet/icons/tryton-timesheet.svg b/modules/timesheet/icons/tryton-timesheet.svg
new file mode 100644
index 0000000000..3a12660397
--- /dev/null
+++ b/modules/timesheet/icons/tryton-timesheet.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/modules/timesheet/line.py b/modules/timesheet/line.py
new file mode 100644
index 0000000000..2953d9338d
--- /dev/null
+++ b/modules/timesheet/line.py
@@ -0,0 +1,357 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import datetime
+
+from sql import Literal
+from sql.aggregate import Max, Sum
+from sql.functions import Extract, CharLength
+
+from trytond.i18n import gettext
+from trytond.model import ModelView, ModelSQL, fields, Unique
+from trytond.wizard import Wizard, StateView, StateAction, Button
+from trytond.pyson import Eval, PYSONEncoder, Date
+from trytond.transaction import Transaction
+from trytond.pool import Pool
+
+from .exceptions import DurationValidationError
+
+
+class Line(ModelSQL, ModelView):
+ 'Timesheet Line'
+ __name__ = 'timesheet.line'
+ company = fields.Many2One('company.company', 'Company', required=True,
+ help="The company on which the time is spent.")
+ employee = fields.Many2One('company.employee', 'Employee', required=True,
+ select=True, domain=[
+ ('company', '=', Eval('company', -1)),
+ ['OR',
+ ('start_date', '=', None),
+ ('start_date', '<=', Eval('date')),
+ ],
+ ['OR',
+ ('end_date', '=', None),
+ ('end_date', '>=', Eval('date')),
+ ],
+ ],
+ depends=['company', 'date'],
+ help="The employee who spends the time.")
+ date = fields.Date('Date', required=True, select=True,
+ help="When the time is spent.")
+ duration = fields.TimeDelta('Duration', 'company_work_time', required=True)
+ work = fields.Many2One('timesheet.work', 'Work',
+ required=True, select=True, domain=[
+ ('company', '=', Eval('company', -1)),
+ ['OR',
+ ('timesheet_start_date', '=', None),
+ ('timesheet_start_date', '<=', Eval('date')),
+ ],
+ ['OR',
+ ('timesheet_end_date', '=', None),
+ ('timesheet_end_date', '>=', Eval('date')),
+ ],
+ ],
+ depends=['date', 'company'],
+ help="The work on which the time is spent.")
+ description = fields.Char('Description',
+ help="Additional description of the work done.")
+ uuid = fields.Char("UUID", readonly=True)
+
+ @classmethod
+ def __setup__(cls):
+ super(Line, cls).__setup__()
+ cls._order = [
+ ('date', 'DESC'),
+ ('id', 'DESC'),
+ ]
+ t = cls.__table__()
+ cls._sql_constraints = [
+ ('uuid_unique', Unique(t, t.uuid),
+ 'timesheet.msg_line_uuid_unique'),
+ ]
+
+ @classmethod
+ def __register__(cls, module_name):
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ update = transaction.connection.cursor()
+ table = cls.__table_handler__(module_name)
+ sql_table = cls.__table__()
+ pool = Pool()
+ Work = pool.get('timesheet.work')
+ work = Work.__table__()
+
+ created_company = not table.column_exist('company')
+
+ super(Line, cls).__register__(module_name)
+
+ # Migration from 3.4: new company field
+ if created_company:
+ # Don't use FROM because SQLite nor MySQL support it.
+ update.execute(*sql_table.update(
+ [sql_table.company], [work.select(work.company,
+ where=work.id == sql_table.work)]))
+ # Migration from 3.4: change hours into timedelta duration
+ if table.column_exist('hours'):
+ table.drop_constraint('check_move_hours_pos')
+ cursor.execute(*sql_table.select(
+ sql_table.id, sql_table.hours))
+ for id_, hours in cursor:
+ duration = datetime.timedelta(hours=hours)
+ update.execute(*sql_table.update(
+ [sql_table.duration],
+ [duration],
+ where=sql_table.id == id_))
+ table.drop_column('hours')
+
+ @staticmethod
+ def default_company():
+ return Transaction().context.get('company')
+
+ @staticmethod
+ def default_employee():
+ User = Pool().get('res.user')
+ employee_id = None
+ if Transaction().context.get('employee'):
+ employee_id = Transaction().context['employee']
+ else:
+ user = User(Transaction().user)
+ if user.employee:
+ employee_id = user.employee.id
+ if employee_id:
+ return employee_id
+
+ @staticmethod
+ def default_date():
+ Date_ = Pool().get('ir.date')
+ return Transaction().context.get('date') or Date_.today()
+
+ @classmethod
+ def validate(cls, lines):
+ super(Line, cls).validate(lines)
+ for line in lines:
+ line.check_duration()
+
+ def check_duration(self):
+ if self.duration < datetime.timedelta():
+ raise DurationValidationError(
+ gettext('timesheet.msg_line_duration_positive',
+ line=self.rec_name))
+
+ @classmethod
+ def copy(cls, lines, default=None):
+ if default is not None:
+ default = default.copy()
+ else:
+ default = {}
+ default.setdefault('uuid')
+ return super().copy(lines, default=default)
+
+ @property
+ def hours(self):
+ return self.duration.total_seconds() / 60 / 60
+
+ def to_json(self):
+ return {
+ 'id': self.id,
+ 'work': self.work.id,
+ 'work.name': self.work.rec_name,
+ 'duration': self.duration.total_seconds(),
+ 'description': self.description,
+ 'uuid': self.uuid,
+ }
+
+
+class EnterLinesStart(ModelView):
+ 'Enter Lines'
+ __name__ = 'timesheet.line.enter.start'
+ employee = fields.Many2One('company.employee', 'Employee', required=True,
+ domain=[
+ ('company', '=', Eval('context', {}).get('company', -1)),
+ ['OR',
+ ('start_date', '=', None),
+ ('start_date', '<=', Eval('date')),
+ ],
+ ['OR',
+ ('end_date', '=', None),
+ ('end_date', '>=', Eval('date')),
+ ],
+ ],
+ depends=['date'],
+ help="The employee who spends the time.")
+ date = fields.Date('Date', required=True,
+ help="When the time is spent.")
+
+ @staticmethod
+ def default_employee():
+ Line = Pool().get('timesheet.line')
+ return Line.default_employee()
+
+ @staticmethod
+ def default_date():
+ Line = Pool().get('timesheet.line')
+ return Line.default_date()
+
+
+class EnterLines(Wizard):
+ 'Enter Lines'
+ __name__ = 'timesheet.line.enter'
+ start = StateView('timesheet.line.enter.start',
+ 'timesheet.line_enter_start_view_form', [
+ Button('Cancel', 'end', 'tryton-cancel'),
+ Button('Enter', 'enter', 'tryton-ok', default=True),
+ ])
+ enter = StateAction('timesheet.act_line_form')
+
+ def do_enter(self, action):
+ pool = Pool()
+ Lang = pool.get('ir.lang')
+ date = self.start.date
+ date = Date(date.year, date.month, date.day)
+ action['pyson_domain'] = PYSONEncoder().encode([
+ ('employee', '=', self.start.employee.id),
+ ('company', '=', self.start.employee.company.id),
+ ('date', '=', date),
+ ])
+ action['pyson_context'] = PYSONEncoder().encode({
+ 'employee': self.start.employee.id,
+ 'company': self.start.employee.company.id,
+ 'date': date,
+ })
+ action['name'] += ' @ %(date)s - %(employee)s' % {
+ 'date': Lang.get().strftime(self.start.date),
+ 'employee': self.start.employee.rec_name,
+ }
+ return action, {}
+
+ def transition_enter(self):
+ return 'end'
+
+
+class HoursEmployee(ModelSQL, ModelView):
+ 'Hours per Employee'
+ __name__ = 'timesheet.hours_employee'
+ employee = fields.Many2One('company.employee', 'Employee')
+ duration = fields.TimeDelta('Duration', 'company_work_time')
+
+ @staticmethod
+ def table_query():
+ pool = Pool()
+ Line = pool.get('timesheet.line')
+ line = Line.__table__()
+ where = Literal(True)
+ if Transaction().context.get('start_date'):
+ where &= line.date >= Transaction().context['start_date']
+ if Transaction().context.get('end_date'):
+ where &= line.date <= Transaction().context['end_date']
+ return line.select(
+ line.employee.as_('id'),
+ Max(line.create_uid).as_('create_uid'),
+ Max(line.create_date).as_('create_date'),
+ Max(line.write_uid).as_('write_uid'),
+ Max(line.write_date).as_('write_date'),
+ line.employee,
+ Sum(line.duration).as_('duration'),
+ where=where,
+ group_by=line.employee)
+
+
+class HoursEmployeeContext(ModelView):
+ 'Hours per Employee Context'
+ __name__ = 'timesheet.hours_employee.context'
+ start_date = fields.Date('Start Date')
+ end_date = fields.Date('End Date')
+
+
+class HoursEmployeeWeekly(ModelSQL, ModelView):
+ 'Hours per Employee per Week'
+ __name__ = 'timesheet.hours_employee_weekly'
+ year = fields.Char('Year')
+ week_internal = fields.Char('Week')
+ week = fields.Function(fields.Char('Week'),
+ 'get_week', searcher='search_week')
+ employee = fields.Many2One('company.employee', 'Employee')
+ duration = fields.TimeDelta('Duration', 'company_work_time')
+
+ @classmethod
+ def __setup__(cls):
+ super(HoursEmployeeWeekly, cls).__setup__()
+ cls._order.insert(0, ('year', 'DESC'))
+ cls._order.insert(1, ('week', 'DESC'))
+ cls._order.insert(2, ('employee', 'ASC'))
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ Line = pool.get('timesheet.line')
+ line = Line.__table__()
+ type_name = cls.year.sql_type().base
+ year_column = Extract('YEAR', line.date).cast(type_name).as_('year')
+ type_name = cls.week_internal.sql_type().base
+ week_column = Extract('WEEK', line.date).cast(type_name).as_(
+ 'week_internal')
+ return line.select(
+ Max(Extract('WEEK', line.date)
+ + Extract('YEAR', line.date) * 100
+ + line.employee * 1000000).as_('id'),
+ Max(line.create_uid).as_('create_uid'),
+ Max(line.create_date).as_('create_date'),
+ Max(line.write_uid).as_('write_uid'),
+ Max(line.write_date).as_('write_date'),
+ year_column,
+ week_column,
+ line.employee,
+ Sum(line.duration).as_('duration'),
+ group_by=(year_column, week_column, line.employee))
+
+ def get_week(self, name):
+ return '%02i' % int(self.week_internal)
+
+ @classmethod
+ def search_week(cls, name, domain):
+ return [('week_internal',) + tuple(domain[1:])]
+
+ @classmethod
+ def order_week(cls, tables):
+ table, _ = tables[None]
+ return [CharLength(table.week_internal), table.week_internal]
+
+
+class HoursEmployeeMonthly(ModelSQL, ModelView):
+ 'Hours per Employee per Month'
+ __name__ = 'timesheet.hours_employee_monthly'
+ year = fields.Char('Year')
+ month = fields.Many2One('ir.calendar.month', "Month")
+ employee = fields.Many2One('company.employee', 'Employee')
+ duration = fields.TimeDelta('Duration', 'company_work_time')
+
+ @classmethod
+ def __setup__(cls):
+ super(HoursEmployeeMonthly, cls).__setup__()
+ cls._order.insert(0, ('year', 'DESC'))
+ cls._order.insert(1, ('month.index', 'DESC'))
+ cls._order.insert(2, ('employee', 'ASC'))
+
+ @classmethod
+ def table_query(cls):
+ pool = Pool()
+ Line = pool.get('timesheet.line')
+ Month = pool.get('ir.calendar.month')
+ line = Line.__table__()
+ month = Month.__table__()
+ type_name = cls.year.sql_type().base
+ year_column = Extract('YEAR', line.date).cast(type_name).as_('year')
+ month_index = Extract('MONTH', line.date)
+ return line.join(month, condition=month_index == month.id).select(
+ Max(Extract('MONTH', line.date)
+ + Extract('YEAR', line.date) * 100
+ + line.employee * 1000000).as_('id'),
+ Max(line.create_uid).as_('create_uid'),
+ Max(line.create_date).as_('create_date'),
+ Max(line.write_uid).as_('write_uid'),
+ Max(line.write_date).as_('write_date'),
+ year_column,
+ month.id.as_('month'),
+ line.employee,
+ Sum(line.duration).as_('duration'),
+ group_by=(year_column, month.id, line.employee))
diff --git a/modules/timesheet/line.xml b/modules/timesheet/line.xml
new file mode 100644
index 0000000000..b5e24a6c9f
--- /dev/null
+++ b/modules/timesheet/line.xml
@@ -0,0 +1,222 @@
+
+
+
+
+
+ timesheet.line
+ form
+ line_form
+
+
+ timesheet.line
+ tree
+ line_tree
+
+
+ Lines
+ timesheet.line
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Timesheet Lines
+ timesheet.line
+
+
+
+
+
+
+
+
+
+
+
+
+
+ form_relate
+ timesheet.work,-1
+
+
+
+
+ User in companies
+
+
+
+
+
+
+
+
+
+ Own timesheet line
+
+
+
+
+
+
+
+
+
+
+ Any timesheet line
+
+
+
+
+
+
+
+
+
+
+ timesheet.line.enter.start
+ form
+ line_enter_start_form
+
+
+
+ Enter Timesheet
+ timesheet.line.enter
+
+
+
+
+ timesheet.hours_employee
+ tree
+ hours_employee_tree
+
+
+ timesheet.hours_employee
+ graph
+ hours_employee_graph
+
+
+ Hours per Employee
+ timesheet.hours_employee
+ timesheet.hours_employee.context
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Own employee hours
+
+
+
+
+
+
+
+
+
+
+ Any employee hours
+
+
+
+
+
+
+
+
+
+
+ timesheet.hours_employee.context
+ form
+ hours_employee_context_form
+
+
+
+ timesheet.hours_employee_weekly
+ tree
+ hours_employee_weekly_tree
+
+
+ Hours per Employee per Week
+ timesheet.hours_employee_weekly
+
+
+
+
+
+
+
+
+
+ timesheet.hours_employee_monthly
+ tree
+ hours_employee_monthly_tree
+
+
+ Hours per Employee per Month
+ timesheet.hours_employee_monthly
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/locale/bg.po b/modules/timesheet/locale/bg.po
new file mode 100644
index 0000000000..9284d3ac57
--- /dev/null
+++ b/modules/timesheet/locale/bg.po
@@ -0,0 +1,405 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Продължителност"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Служител"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Крайна дата"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Начална дата"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Продължителност"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Служител"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Месец"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Година"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Продължителност"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Служител"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Седмица"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Седмица"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Година"
+
+#, fuzzy
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Фирма"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Описание"
+
+#, fuzzy
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Продължителност"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Служител"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Задача"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Служител"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Фирма"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Име"
+
+#, fuzzy
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Източник"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Ред от график"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Редове от график"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Задача"
+
+#, fuzzy
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "От дата"
+
+#, fuzzy
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "До дата"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Редове от график"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Зачади"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Часове на служител"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Зачади"
+
+#, fuzzy
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Часове на служител"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Часове за служител за месец"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Часове на служител за седмица"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Ред от график"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Задача"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Управление на графици"
+
+#, fuzzy
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Продължителност"
+
+#, fuzzy
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Продължителност"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Отказ"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Въвеждане"
diff --git a/modules/timesheet/locale/ca.po b/modules/timesheet/locale/ca.po
new file mode 100644
index 0000000000..09e60de5b8
--- /dev/null
+++ b/modules/timesheet/locale/ca.po
@@ -0,0 +1,373 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Temps"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Empleat"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Data final"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Data inicial"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Temps"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Empleat"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Mes"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Any"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Temps"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Empleat"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Setmana"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Setmana"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Any"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Descripció"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Temps"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Empleat"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "Identificador únic universal"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Treball"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Empleat"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Temps del full de treball"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nom"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Origen"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Final del full de treball"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Línies del full de treball"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Inici del full de treball"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Treball"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Des de la data"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Fins a la data"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "L'empresa on s'ha invertit el temps."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Quan s'ha realitzat el treball."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Descripció addicional del treball realitzat."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "El empleat que realitzat el treball."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "El treball en que s'ha invertit el temps."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Quan s'han realitzat els treballs."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "L'empleat que ha realitzat els treballs."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Feu que el treball pertanyi a l'empresa."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Temps total dedicat en aquest treball."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "El identificador principal del treball."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Utilitzeu-ho per relacionar el temps invertit amb altres registres."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "Restringeix afegir línies desprès de la data."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Inverteix temps en aquest treball."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "Restringeix afegir línies abans de la data."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "No tinguis en compte les línies abans de la data."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "No tinguis en compte les línies desprès de la data."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hores per empleat"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hores per empleat i mes"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hores per empleat i setmana"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Afegeix full de treball"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Línies"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Línies del full de treball"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Treballs"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Treballs"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr "Tot"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr "Obert"
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "La duració de la línia \"%(line)s\" ha de ser positiva."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "El UUID de la línia del full de treball ha de ser únic."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+"Les empreses associades amb el treball \"%(work)s\" i les del seu origen "
+"difereixen."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "L'origen ha de ser únic per empresa."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "Qualsevol hora d'empleat"
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "Hores del propi empleat"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "Línies del full de treball pròpies"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "Qualsevol línia del full de treball"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Usuari a les empreses"
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Usuari a les empreses"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuració"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hores per empleat"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hores per empleat i mes"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hores per empleat i setmana"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Afegeix full de treball"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Línies"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Informes"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Full de treball"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Treballs"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Treballs"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Administració de fulls de treball"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Hores per empleat"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Context hores per empleat"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Hores per empleat i mes"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Hores per empleat i setmana"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Línia de full de treball"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Afegir línies"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Treball"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Context treball"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Full de treball"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Temps"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Temps"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Cancel·la"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Afegeix"
diff --git a/modules/timesheet/locale/cs.po b/modules/timesheet/locale/cs.po
new file mode 100644
index 0000000000..7881b71729
--- /dev/null
+++ b/modules/timesheet/locale/cs.po
@@ -0,0 +1,382 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr ""
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Namu"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Timesheet Lines"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr ""
diff --git a/modules/timesheet/locale/de.po b/modules/timesheet/locale/de.po
new file mode 100644
index 0000000000..69d8393ef3
--- /dev/null
+++ b/modules/timesheet/locale/de.po
@@ -0,0 +1,374 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Dauer"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Mitarbeiter"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Enddatum"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Startdatum"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Dauer"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Mitarbeiter"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Monat"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Jahr"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Dauer"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Mitarbeiter"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Woche"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Woche"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Jahr"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Unternehmen"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Beschreibung"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Dauer"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Mitarbeiter"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "UUID"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Tätigkeit"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Mitarbeiter"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Unternehmen"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Zeiterfassung Dauer"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Name"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Herkunft"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Zeiterfassung Ende"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Zeiterfassungspositionen"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Zeiterfassung Beginn"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Tätigkeit"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Von Datum"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Bis Datum"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "Das Unternehmen für das Zeit aufgewendet wird."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Wann die Zeit aufgewendet wird."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Zusätzliche Beschreibung der erledigten Aufgabe."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "Der Mitarbeiter der Zeit aufwendet."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "Die Aufgabe für die Zeit aufgewendet wird."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Wann die Zeit aufgewendet wird."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "Der Mitarbeiter der Zeit aufwendet."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Die Aufgabe dem Unternehmen zuordnen."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Die gesamte Zeit, die für diese Aufgabe verwendet wurde."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "Das Hauptidentifizierungsmerkmal der Aufgabe."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Die aufgewendete Zeit auf andere Datensätze anwenden."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "Hinzufügen von Positionen nach dem Datum beschränken."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Zeit für diese Aufgabe aufwenden."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "Hinzufügen von Positionen vor dem Datum beschränken."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "Buchungspositionen vor dem Datum nicht berücksichtigen."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "Buchungspositionen nach dem Datum nicht berücksichtigen."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Stunden pro Mitarbeiter"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Stunden pro Mitarbeiter und Monat"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Stunden pro Mitarbeiter und Woche"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Zur Zeiterfassung"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Zeiterfassungspositionen"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Zeiterfassungspositionen"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Tätigkeiten"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Tätigkeiten"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr "Alle"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr "Offen"
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "Die Zeitdauer von Zeile \"%(line)s\" muss positiv sein."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "Die UUID der Zeiterfassungsposition muss eindeutig sein."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+"Die mit der Tätigkeit \"%(work)s\" und Ihrer Herkunft verknüpften "
+"Unternehmen weichen voneinander ab."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+"Die Herkunft der Arbeit pro Unternehmen kann nur einmal vergeben werden."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "Alle Mitarbeiterzeiten"
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "Zeiterfassungspositionen Auswertung (Nur Eigene)"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "Eigene Zeiterfassungspositionen"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "Alle Zeiterfassungspositionen"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Benutzer im Unternehmen"
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Benutzer im Unternehmen"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Einstellungen"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Stunden pro Mitarbeiter"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Stunden pro Mitarbeiter und Monat"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Stunden pro Mitarbeiter und Woche"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Zur Zeiterfassung"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Zeiterfassungspositionen"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Auswertungen"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Zeiterfassung"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Tätigkeiten"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Tätigkeiten"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Zeiterfassung Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Stunden pro Mitarbeiter"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Stunden pro Mitarbeiter Kontext"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Stunden pro Mitarbeiter und Monat"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Stunden pro Mitarbeiter und Woche"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Zeiterfassungsposition"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Eingabe Zeilen"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Tätigkeit"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Tätigkeit Kontext"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Zeiterfassung"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Dauer"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Dauer"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Zur Eingabe"
diff --git a/modules/timesheet/locale/es.po b/modules/timesheet/locale/es.po
new file mode 100644
index 0000000000..222b921c81
--- /dev/null
+++ b/modules/timesheet/locale/es.po
@@ -0,0 +1,372 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Tiempo"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Empleado"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Fecha final"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Fecha inicial"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Tiempo"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Empleado"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Mes"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Año"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Tiempo"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Empleado"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Semana"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Semana"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Año"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Descripción"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Tiempo"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Empleado"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "Identificador único universal"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Trabajo"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Empleado"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Duración del parte de trabajo"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nombre"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Origen"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Fin de parte de trabajo"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Líneas de parte de trabajo"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Inicio de parte de trabajo"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Trabajo"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "De la fecha"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Hasta la fecha"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "La empresa en la que se ha invertido el tiempo."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Cuando se ha realizado el trabajo."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Descripción adicional del trabajo realizado."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "El empleado que ha realizado el trabajo."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "El trabajo en que se ha invertido el tiempo."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Cuando se han realizado los trabajos."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "El empleado que ha realizado los trabajos."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Hace que el trabajo pertenezca a la empresa."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Tiempo total dedicado en este trabajo."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "El identificador principal del trabajo."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Utilizado para relacionar el tiempo invertido con otros registros."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "No permite añadir líneas después de esta fecha."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Invierte tiempo en este trabajo."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "No permite añadir líneas antes de esta fecha."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "No tiene en cuenta las lineas antes de la fecha."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "No tiene en cuenta las lineas después de la fecha."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Horas por empleado"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Horas por empleado y mes"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empleado y semana"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Añadir parte de trabajo"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Líneas"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Líneas del parte de trabajo"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Trabajos"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Trabajos"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr "Todo"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr "Abierto"
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "La duración de la línea \"%(line)s\" debe ser positiva."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "El UUID de la linea del parte de trabajo tiene que ser único."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+"Las empresas asociadas con el trabajo \"%(work)s\" y sus orígenes difieren."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "El origen debe ser único por empresa."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "Cualquier hora de empleado"
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "Horas del propio empleado"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "Lineas del parte de trabajo propias"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "Cualquier línea del parte de trabajo"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Usuario en las empresas"
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Usuario en las empresas"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuración"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Horas por empleado"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Horas por empleado y mes"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empleado y semana"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Añadir parte de trabajo"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Líneas"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Informes"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Partes de trabajo"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Trabajos"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Trabajos"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Administración de partes de trabajo"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Horas por empleado"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Contexto de Horas por empleado"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Horas por empleado y mes"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empleado y semana"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Línea de parte de trabajo"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Añadir líneas"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Trabajo"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Contexto trabajo"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Parte de trabajo"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Tiempo"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Tiempo"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Añadir"
diff --git a/modules/timesheet/locale/es_419.po b/modules/timesheet/locale/es_419.po
new file mode 100644
index 0000000000..87f66a1cd6
--- /dev/null
+++ b/modules/timesheet/locale/es_419.po
@@ -0,0 +1,378 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Duración"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Duración"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Duración"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr ""
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Duración"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Horas por empleado por mes"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empleado por semana"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Ingresar líneas"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "La duración de la línea \"%(line)s\" debe ser positiva."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Horas por empleado por mes"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empleado por semana"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Ingresar líneas"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Contexto de horas por empleado"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Horas por empleado por mes"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empleado por semana"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr ""
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Ingresar líneas"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr ""
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Contexto de trabajo"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr ""
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Duración"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Duración"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Ingresar"
diff --git a/modules/timesheet/locale/et.po b/modules/timesheet/locale/et.po
new file mode 100644
index 0000000000..bc2a989b74
--- /dev/null
+++ b/modules/timesheet/locale/et.po
@@ -0,0 +1,376 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Kestvus"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Töötaja"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Lõppkuupäev"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Alguskuupäev"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Kestvus"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Töötaja"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Kuu"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Aasta"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Kestvus"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Töötaja"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Nädal"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Nädal"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Aasta"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Ettevõte"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Kuupäev"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Kirjeldus"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Kestvus"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Töötaja"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "UUID"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Töö"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Kuupäev"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Töötaja"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Ettevõte"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Töögraafiku kestvus"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nimi"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Päritolu"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Töögraafiku lõpp"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Töögraafiku read"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Töögraafiku algus"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Töö"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Kuupäevast"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Kuupäevani"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Millal aega kulutati"
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Millal aega kulutati"
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Sellele tööle kulunud aeg"
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Tunde töötaja kohta"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Tunde töötaja kohta kuus"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Tunde töötaja kohta nädalas"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Sisesta töögraafik"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Lisa read"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Töögraafiku read"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Tööd"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Tööd"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "Kõigi töötajate tunnid"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "Oma töötaja tunnid"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "Oma tööajatabeli rida"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "Kõik tööaja tabeli read"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Kasutaja ettevõttes"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Kasutaja ettevõttes"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Seadistus"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Tunde töötaja kohta"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Tunde töötaja kohta kuus"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Tunde töötaja kohta nädalas"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Sisesta töögraafik"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Lisa read"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Aruandlus"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Töögraafik"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Tööd"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Tööd"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Töögraafiku administreerimine"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Tunde töötaja kohta"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Tunde töötaja kohta sisu"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Tunde töötaja kohta kuus"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Tunde töötaja kohta nädalas"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Töögraafiku rida"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Lisa read"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Töö"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Töö sisu"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Töögraafik"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Kestvus"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Kestvus"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Tühista"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Sisesta"
diff --git a/modules/timesheet/locale/fa.po b/modules/timesheet/locale/fa.po
new file mode 100644
index 0000000000..8378c1848c
--- /dev/null
+++ b/modules/timesheet/locale/fa.po
@@ -0,0 +1,373 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "مدت زمان"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "کارمند"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "تاریخ پایان"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "تاریخ شروع"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "مدت زمان"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "کارمند"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "ماه"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "سال"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "مدت زمان"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "کارمند"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "هفته"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "هفته"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "سال"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "شرکت"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "تاریخ"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "شرح"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "مدت زمان"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "کارمند"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "شناسه کاربری"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "کار"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "تاریخ"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "کارمند"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "شرکت"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "طول مدت جدول زمانی"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "نام"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "مبداء"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "پایان جدول زمانی"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "سطرهای جدول زمانی"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "شروع جدول زمانی"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "کار"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "از تاریخ"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "تا تاریخ"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "شرکتی که مدت زمان در آن صرف شده است."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "هنگامی که زمان صرف شد."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "توضیحات اضافی کارانجام شده."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "کارمندی که زمان را صرف می کند."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "کاری که زمان صرف آن شده است."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "هنگامی که زمان صرف شد."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "کارمندی که زمان را صرف می کند."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "کار متعلق به شرکت را بسازید."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "کل زمان صرف شده در این کار."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "شناسه اصلی کار."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "استفاده از مرتبط کردن زمان صرف شده برای پرونده های دیگر."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "محدودیت افزودن سطرهای بعد از تاریخ."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "زمان صرف شده روی این کار."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "محدودیت افزودن سطرهای قبل از تاریخ."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "سطرهای قبل از تاریخ را حساب نکنید."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "سطرهای بعد از تاریخ را حساب نکنید."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "ساعات در هر کارمند"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "ساعات در هر کارمند در هر ماه"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "ساعات در هر کارمند در هر هفته"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "جدول زمانی را وارد کنید"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "واردکردن سطرها"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "سطرهای جدول زمانی"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "کارها"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "کارها"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "طول مدت سطر : \"%s\" باید مثبت باشد."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "شناسه کاربر در جدول زمانی باید منحصربفرد باشد."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr "شرکت های مرتبط با کار:\"%(work)s\" و منشاء آنها متفاوت است."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "منشا باید در هر شرکتی منحصر به فرد باشد."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "پیکربندی"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "ساعات در هر کارمند"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "ساعات در هر کارمند در هر ماه"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "ساعات در هر کارمند در هر هفته"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "جدول زمانی را وارد کنید"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "واردکردن سطرها"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "گزارش گیری"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "جدول زمانی"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "کارها"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "کارها"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "مدیریت جدول زمانی"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "ساعات در هر کارمند"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "مفاد ساعات در هر کارمند"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "ساعات در هر کارمند در هر ماه"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "ساعات در هر کارمند در هر هفته"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "سطر جدول زمانی"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "واردکردن سطرها"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "کار"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "مفاد کار"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "جدول زمانی"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "مدت زمان"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "مدت زمان"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "انصراف"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "وارد کردن"
diff --git a/modules/timesheet/locale/fi.po b/modules/timesheet/locale/fi.po
new file mode 100644
index 0000000000..6c729b6651
--- /dev/null
+++ b/modules/timesheet/locale/fi.po
@@ -0,0 +1,381 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr ""
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Timesheet Lines"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr ""
diff --git a/modules/timesheet/locale/fr.po b/modules/timesheet/locale/fr.po
new file mode 100644
index 0000000000..32259ad08f
--- /dev/null
+++ b/modules/timesheet/locale/fr.po
@@ -0,0 +1,372 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Durée"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Employé"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Date de fin"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Date de début"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Durée"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Employé"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Mois"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Année"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Durée"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Employé"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Semaine"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Semaine"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Année"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Société"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Date"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Description"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Durée"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Employé"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "UUID"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Travail"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Date"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Employé"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Société"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Durée de feuille de présence"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nom"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Origine"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Fin de feuille de présence"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Lignes de feuille de présence"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Début de feuille de présence"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Travail"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Date de début"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Date de fin"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "La société pour laquelle le temps est passé."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Quand le temps a été passé."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Description additionnelle du travail réalisé."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "L'employé qui a passé le temps."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "Le travail sur lequel le temps est passé."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Quand le temps a été passé."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "L'employé qui a passé le temps."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Faire appartenir le travail à la société."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Temps total passé sur ce travail."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "L'identifiant principal du travail."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Sert à lier le temps passé sur d'autres enregistrements."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "Restreint l'ajout de lignes après la date."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Passer du temps sur ce travail."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "Restreint l'ajout de lignes avant la date."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "Ne pas prendre en compte les lignes avant la date."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "Ne pas prendre en compte les lignes après la date."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Heures par employé"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Heures par employé par mois"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Heures par employé par semaine"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Entrer une feuille de présence"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Lignes"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Lignes de feuille de présence"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Travaux"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Travaux"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr "Tous"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr "Ouverts"
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "La durée de la ligne « %(line)s » doit être positive."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "L'UUID des lignes de feuille de présence doit être unique."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+"Les sociétés associées au travail « %(work)s » et son origine diffèrent."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "L'origine doit être unique par société."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "N'importe quelles heures d'employés"
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "Heures d'employé propre"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "Ligne de feuille de présence propre"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "N'importe quelle ligne de feuille de présence"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Utilisateur dans les sociétés"
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Utilisateur dans les sociétés"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Heures par employé"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Heures par employé par mois"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Heures par employé par semaine"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Entrer un feuille de présence"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Lignes"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Rapports"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Feuilles de présence"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Travaux"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Travaux"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Administration des feuilles de présence"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Heures par employé"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Heures par employé contexte"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Nombre d'heures par employé par mois"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Nombre d'heures par employé par semaine"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Ligne de feuille de présence"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Entrer les lignes"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Travail"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Travail contexte"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Feuilles de présence"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Durée"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Durée"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Annuler"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Entrer"
diff --git a/modules/timesheet/locale/hu.po b/modules/timesheet/locale/hu.po
new file mode 100644
index 0000000000..fb8bead163
--- /dev/null
+++ b/modules/timesheet/locale/hu.po
@@ -0,0 +1,397 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Időtartam"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Alkalmazott"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Időtartam"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Alkalmazott"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Hónap"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Év"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Időtartam"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Alkalmazott"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Hét"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Hét"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Év"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Társaság"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Dátum"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Leírás"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Időtartam"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Alkalmazott"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Tevékenység"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Dátum"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Alkalmazott"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Társaság"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Időtartam"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Név"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Időkövetés vége"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Időpozíció"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Időkövetés kezdete"
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Tevékenység"
+
+#, fuzzy
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Dátumtól"
+
+#, fuzzy
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Dátumig"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+#, fuzzy
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Az össz idő, ami erre a feladatra lett fordítva"
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Sor hozzáadás"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Tevékenység"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "A \"%(line)s\" sor időtartama pozitívnak kell lenni."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Óra per alkalmazott"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Sor hozzáadás"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Tevékenység"
+
+#, fuzzy
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Óra per alkalmazott"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Óra per alkalmazott per hónap"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Óra per alkalmazott per hét"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Időpozíció"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Sor hozzáadás"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Tevékenység"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Időtartam"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Időtartam"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Időtartam"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Mégse"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Bevitel"
diff --git a/modules/timesheet/locale/id.po b/modules/timesheet/locale/id.po
new file mode 100644
index 0000000000..918af81619
--- /dev/null
+++ b/modules/timesheet/locale/id.po
@@ -0,0 +1,373 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Pegawai"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Tanggal Akhir"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Tanggal Awal"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Bulan"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Tahun"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Pegawai"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Pekan"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Pekan"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Tahun"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Perusahaan"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Tanggal"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Deskripsi"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Pegawai"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "UUID"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Kerja"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Tanggal"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Pegawai"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Perusahaan"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nama"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Asal"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Kerja"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Dari Tanggal"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Ke Tanggal"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Pengguna di dalam perusahaan"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Pengguna di dalam perusahaan"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Konfigurasi"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Pelaporan"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr ""
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr ""
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr ""
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr ""
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Batal"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr ""
diff --git a/modules/timesheet/locale/it.po b/modules/timesheet/locale/it.po
new file mode 100644
index 0000000000..c5a8fc6efe
--- /dev/null
+++ b/modules/timesheet/locale/it.po
@@ -0,0 +1,391 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Durata"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Dipendente"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Data finale"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Data iniziale"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Durata"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Dipendente"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Mese"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Anno"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Durata"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Dipendente"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Settimana"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Settimana"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Anno"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Azienda"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Descrizione"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Durata"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Dipendente"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Lavoro"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Dipendente"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Azienda"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Durata timesheet"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nome"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Origine"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Fine timesheet"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Righe timesheet"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "inizio timesheet"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Lavoro"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Data Iniziale"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "a data"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+#, fuzzy
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Tempo totale speso nel lavoro"
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Inserire righe"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "La durata della riga \"%(line)s\" dev'essere positiva."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "L'origine dev'essere unica per azienda."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configurazione"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Inserire righe"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Ore per dipendente"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Context ore per dipendente"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Ore per dipendente per mese"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Ore per dipendente per settimana"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Riga timesheet"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Inserire righe"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Lavoro"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Context lavoro"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Durata"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Durata"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Annulla"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Invio"
diff --git a/modules/timesheet/locale/lo.po b/modules/timesheet/locale/lo.po
new file mode 100644
index 0000000000..c37e71249d
--- /dev/null
+++ b/modules/timesheet/locale/lo.po
@@ -0,0 +1,393 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "ກຳນົດເວລາ"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "ພະນັກງານ"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "ວັນທີສິ້ນສຸດ"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "ວັນທີເລີ່ມ"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "ກຳນົດເວລາ"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "ພະນັກງານ"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "ເດືອນ"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "ປີ"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "ກຳນົດເວລາ"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "ພະນັກງານ"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "ອາທິດ"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "ອາທິດ"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "ປີ"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "ບໍລິສັດ"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "ວັນທີ"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "ຄຳອະທິບາຍ"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "ກຳນົດເວລາ"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "ພະນັກງານ"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "ເຮັດວຽກ"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "ວັນທີ"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "ພະນັກງານ"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "ສູນການແພດ"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "ກຳນົດເວລາຂອງຕາຕະລາງເວລາ"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "ຊື່"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "ໜ້າວຽກເດີມ"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "ຕາຕະລາງເຮັດວຽກຈົບເວລາ"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "ລາຍການຕາຕະລາງເຮັດວຽກ"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "ຕາຕະລາງເຮັດວຽກເລີ້ມເວລາ"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "ວຽກ"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "ແຕ່ວັນທີ"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "ເຖິງວັນທີ"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+#, fuzzy
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "ເວລາທັງໝົດທີ່ໃຊ້ໃນໜ້າວຽກນີ້"
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "ເຂົ້າແຖວຕາຕະລາງ"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "ກຳນົດເວລາຂອງ \"%(line)s\" ຕ້ອງເປັນບວກ"
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "ສຳນັກງານເດີມຕ້ອງບໍ່ຊໍ້າກັນ."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "ເຂົ້າແຖວຕາຕະລາງ"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "ຊົ່ວໂມງຕໍ່ພະນັກງານຜູ້ນື່ງ"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "ຕົວຈິງຊົ່ວໂມງຕໍ່ພະນັກງານ"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "ຊົ່ວໂມງຕໍ່ພະນັກງານຜູ້ນື່ງຕໍ່ເດືອນ"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "ຊົ່ວໂມງຕໍ່ພະນັກງານຜູ້ນື່ງຕໍ່ອາທິດ"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "ຖັນແຖນຕາຕະລາງເຮັດວຽກ"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "ເຂົ້າແຖວຕາຕະລາງ"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "ເຮັດວຽກ"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "ວຽກຕົວຈິງ"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "ຕາຕະລາງເຮັດວຽກ"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "ກຳນົດເວລາ"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "ກຳນົດເວລາ"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "ຍົກເລີກ"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "ເຂົ້າໄປ"
diff --git a/modules/timesheet/locale/lt.po b/modules/timesheet/locale/lt.po
new file mode 100644
index 0000000000..f0e33c4e1a
--- /dev/null
+++ b/modules/timesheet/locale/lt.po
@@ -0,0 +1,382 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Pabaigos data"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Pradžios data"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Organizacija"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr ""
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Organizacija"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Namu"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Nuostatos"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Ataskaitos"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Timesheet Lines"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr ""
diff --git a/modules/timesheet/locale/nl.po b/modules/timesheet/locale/nl.po
new file mode 100644
index 0000000000..1bb12b2559
--- /dev/null
+++ b/modules/timesheet/locale/nl.po
@@ -0,0 +1,377 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "duur"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Werknemer"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Eind datum"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Start datum"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "duur"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Werknemer"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Maand"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Jaar"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "duur"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Werknemer"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Week"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Week"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Jaar"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Bedrijf"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Specificatie"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "duur"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Werknemer"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "UUID"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Werk"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Werknemer"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Bedrijf"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Urenstaatduur"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Naam"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Oorsprong"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Einde urenstaat"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Tijdregistratieregels"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Start urenstaat"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Werk"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Vanaf datum"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Tot datum"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "Het bedrijf waaraan de tijd wordt besteed."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Wanneer de tijd is besteed."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Aanvullende beschrijving van het verrichte werk."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "De werknemer die de tijd spendeerde."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "Het werk waaraan de tijd wordt besteed."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Wanneer de tijd is besteed."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "De werknemer die de tijd spendeerde."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Wijs het werk toe aan het bedrijf."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Totale tijd besteed aan dit werk."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "De hoofdidentificatie van het werk."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Gebruiken om de bestede tijd te relateren aan andere records."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "Beperk het toevoegen van regels na de datum."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Besteed tijd aan dit werk."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "Beperk het toevoegen van regels vóór de datum."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "Houd geen rekening met regels vóór de datum."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "Houd geen rekening met regels vóór de datum."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Uren per werknemer"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Uren per werknemer per maand"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Uren per werknemer per week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Voer urenstaat in"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Lijnen invoeren"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Tijdregistratieregels"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Werken"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Werken"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "De duur van regel \"%(line)s\" moet positief zijn."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "De UUID van de urenstaatregel moet uniek zijn."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+"De bedrijven geassocieerd met werk \"%(work)s\" en de oorsprong ervan "
+"verschillen."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "De oorsprong van het werk moet uniek zijn per bedrijf."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "alle medewerkers uren"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "Eigen medewerkersuren"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "Eigen urenstaatregel"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "eender welke tijdregistratieregel"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "Gebruiker in bedrijven"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "Gebruiker in het bedrijf"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Instellingen"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Uren per werknemer"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Uren per werknemer per maand"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Uren per werknemer per week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Voer urenstaat in"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Lijnen invoeren"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Rapporten"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Tijdsregistratie"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Werken"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Werken"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Urenregistratie administratie"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Uren per werknemer"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Uren per werknemer context"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Uren per werknemer per maand"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Uren per werknemer per week"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Tijdregistratieregel"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Lijnen invoeren"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Werk"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Werk context"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Tijdsregistratie"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "duur"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "duur"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Annuleer"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "invoeren"
diff --git a/modules/timesheet/locale/pl.po b/modules/timesheet/locale/pl.po
new file mode 100644
index 0000000000..4bd82f6963
--- /dev/null
+++ b/modules/timesheet/locale/pl.po
@@ -0,0 +1,387 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Czas trwania"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Pracownik"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Data ukończenia"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Data rozpoczęcia"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Czas trwania"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Pracownik"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Miesiąc"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Rok"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Czas trwania"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Pracownik"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Tydzień"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Tydzień"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Rok"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Firma"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Opis"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Czas trwania"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Pracownik"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Praca"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Pracownik"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Firma"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nazwa"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Źródło"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Praca"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Od dnia"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Do dnia"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Całkowity czas pracy"
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Konfiguracja"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Godziny wg pracownika"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Godziny wg pracownika na miesiąc"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Godziny wg pracownika na tydzień"
+
+#, fuzzy
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Timesheet Lines"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Praca"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Czas trwania"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Czas trwania"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Anuluj"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Wprowadź"
diff --git a/modules/timesheet/locale/pt.po b/modules/timesheet/locale/pt.po
new file mode 100644
index 0000000000..e6b2dfa548
--- /dev/null
+++ b/modules/timesheet/locale/pt.po
@@ -0,0 +1,387 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Duração"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Empregado"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Data de término"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Data de início"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Duração"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Empregado"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Mês"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Ano"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Duração"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Empregado"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Semana"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Semana"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Ano"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Descrição"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Duração"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Empregado"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Trabalho"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Empregado"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Duração do Horário de Trabalho"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Nome"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Origem"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Fim do Horário de Trabalho"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Linhas do Horário de Trabalho"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Início do Horário de Trabalho"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Trabalho"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Desde a Data"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "À Data"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "A empresa à qual o tempo é gasto."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Quando o tempo é gasto."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Descrição adicional do trabalho feito."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "O empregado que gasta o tempo."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "O trabalho no qual o tempo é gasto."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Quando o tempo é gasto."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "O empregado que gasta o tempo."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Faça com que o trabalho pertença à empresa."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Tempo total gasto neste trabalho."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "O identificador principal do trabalho."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Use para relacionar o tempo gasto a outros registros."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "Restringe a adição de lançamentos após a data."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Tempo gasto neste trabalho."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "Restringe a adição de lançamentos antes da data."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "Não considere lançamentos antes da data."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "Não considere lançamentos após a data."
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Digite Quadro de Horários"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Inserir as Linhas"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Linhas do Horário de Trabalho"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "A duração da linha \"%(line)s\" deve ser positiva."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "A UUID da linha do horário de trabalho deve ser única."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "A origem deve ser única por empresa."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Digite Quadro de Horários"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Inserir as Linhas"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Quadro de Horários"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Administração do Quadro de Horários"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Horas por Empregado"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Contexto das Horas por Empregado"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Horas por Empregado por Semana"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Horas por empregado por semana"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Linha de Horário de Trabalho"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Inserir as Linhas"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Trabalho"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Contexto do Trabalho"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Quadro de Horários"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Duração"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Duração"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Inserir"
diff --git a/modules/timesheet/locale/ro.po b/modules/timesheet/locale/ro.po
new file mode 100644
index 0000000000..16e4f725c7
--- /dev/null
+++ b/modules/timesheet/locale/ro.po
@@ -0,0 +1,371 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Companie"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Descriere"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Angajat"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Companie"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Origine"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr ""
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configurare"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Raportare"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr ""
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr ""
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr ""
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr ""
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr ""
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr ""
diff --git a/modules/timesheet/locale/ru.po b/modules/timesheet/locale/ru.po
new file mode 100644
index 0000000000..e4b7411674
--- /dev/null
+++ b/modules/timesheet/locale/ru.po
@@ -0,0 +1,405 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Продолжительность"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Сотрудник"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Дата окончания"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Дата начала"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Продолжительность"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Сотрудник"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Месяц"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Год"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Продолжительность"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Сотрудник"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Неделя"
+
+#, fuzzy
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Неделя"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Год"
+
+#, fuzzy
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Учет.орг."
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Описание"
+
+#, fuzzy
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Продолжительность"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Сотрудник"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Работа"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Сотрудник"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Организация"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Наименование"
+
+#, fuzzy
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Первоисточник"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Конечная дата табеля"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Строки табеля"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Начальная дата табеля"
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Работа"
+
+#, fuzzy
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Начальная дата"
+
+#, fuzzy
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Конечная дата"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Ввод строк"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Работы"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Часы на сотрудника"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Ввод строк"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Работы"
+
+#, fuzzy
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Часы на сотрудника"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Часы на сотрудника в месяц"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Часы на сотрудника в неделю"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Строка табеля"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Ввод строк"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Работа"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Табель"
+
+#, fuzzy
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Продолжительность"
+
+#, fuzzy
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Продолжительность"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Отменить"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Ввод"
diff --git a/modules/timesheet/locale/sl.po b/modules/timesheet/locale/sl.po
new file mode 100644
index 0000000000..01b1c934bc
--- /dev/null
+++ b/modules/timesheet/locale/sl.po
@@ -0,0 +1,392 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "Trajanje"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "Zaposlenec"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "Konec"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "Začetek"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "Trajanje"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "Zaposlenec"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "Mesec"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "Leto"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "Trajanje"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "Zaposlenec"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "Teden"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "Teden"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "Leto"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "Družba"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "Opis"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "Trajanje"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "Zaposlenec"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Naloga"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "Zaposlenec"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "Družba"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "Trajanje"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "Naziv"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "Poreklo"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Konec evidence"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Evidenca dela"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "Začetek evidence"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Naloga"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "Od"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "Do"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "Družba, za katero se evidentira čas."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "Dan evidentiranja časa."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "Dodaten opis opravljene naloge."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "Zaposlenec, katerega čas se evidentira."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "Naloga, za katero se evidentira čas."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "Dan evidentiranja časa."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "Zaposlenec, katerega čas se evidentira."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "Poveži nalogo z družbo."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "Skupen čas, porabljen na nalogi."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "Glavni identifikator naloge."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "Povezuje porabljen čas z drugimi zapisi."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "Evidentiranje časa po tem datumu je onemogočeno."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "Evidentiranje časa, porabljenega na nalogi."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "Evidentiranje časa pred tem datumom je onemogočeno."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "Evidenca časa pred tem datumom se ne upošteva."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "Evidenca časa po tem datumu se ne upošteva."
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "Vnos postavk"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "Trajanje na postavki \"%(line)s\" mora biti nenegativno."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "Poreklo mora biti unikatno po družbi."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "Vnos postavk"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+#, fuzzy
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Ure po zaposlencu"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "Ure po kontekstu zaposlenca"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Mesečne ure po zaposlencu"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Tedenske ure po zaposlencu"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Evidenca dela"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "Vnos postavk"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Naloga"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "Kontekst naloge"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Evidenca dela"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "Trajanje"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "Trajanje"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "Prekliči"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "Vnos"
diff --git a/modules/timesheet/locale/tr.po b/modules/timesheet/locale/tr.po
new file mode 100644
index 0000000000..5406568494
--- /dev/null
+++ b/modules/timesheet/locale/tr.po
@@ -0,0 +1,383 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr ""
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr ""
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr ""
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr ""
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "Timesheet Lines"
+
+#, fuzzy
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr ""
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr ""
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr ""
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr ""
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr ""
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr ""
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr ""
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "Timesheet Lines"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "İş \"%(line)s\" süresi pozitif olmalı"
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr ""
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "Kök her firma için özgün olmalı."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "Configuration"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "Enter Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "Reporting"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "Works"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "Timesheet Administration"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "Hours per Employee"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "Hours per Employee per Month"
+
+#, fuzzy
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "Hours per Employee per Week"
+
+#, fuzzy
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "Timesheet Lines"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "Works"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr ""
+
+#, fuzzy
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "Timesheet"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr ""
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr ""
diff --git a/modules/timesheet/locale/zh_CN.po b/modules/timesheet/locale/zh_CN.po
new file mode 100644
index 0000000000..8e7a334540
--- /dev/null
+++ b/modules/timesheet/locale/zh_CN.po
@@ -0,0 +1,376 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:timesheet.hours_employee,duration:"
+msgid "Duration"
+msgstr "持续时间"
+
+msgctxt "field:timesheet.hours_employee,employee:"
+msgid "Employee"
+msgstr "雇员"
+
+msgctxt "field:timesheet.hours_employee.context,end_date:"
+msgid "End Date"
+msgstr "结束日期"
+
+msgctxt "field:timesheet.hours_employee.context,start_date:"
+msgid "Start Date"
+msgstr "开始日期"
+
+msgctxt "field:timesheet.hours_employee_monthly,duration:"
+msgid "Duration"
+msgstr "持续时间"
+
+msgctxt "field:timesheet.hours_employee_monthly,employee:"
+msgid "Employee"
+msgstr "雇员"
+
+msgctxt "field:timesheet.hours_employee_monthly,month:"
+msgid "Month"
+msgstr "月"
+
+msgctxt "field:timesheet.hours_employee_monthly,year:"
+msgid "Year"
+msgstr "年"
+
+msgctxt "field:timesheet.hours_employee_weekly,duration:"
+msgid "Duration"
+msgstr "持续时间"
+
+msgctxt "field:timesheet.hours_employee_weekly,employee:"
+msgid "Employee"
+msgstr "雇员"
+
+msgctxt "field:timesheet.hours_employee_weekly,week:"
+msgid "Week"
+msgstr "周"
+
+msgctxt "field:timesheet.hours_employee_weekly,week_internal:"
+msgid "Week"
+msgstr "周"
+
+msgctxt "field:timesheet.hours_employee_weekly,year:"
+msgid "Year"
+msgstr "年"
+
+msgctxt "field:timesheet.line,company:"
+msgid "Company"
+msgstr "公司"
+
+msgctxt "field:timesheet.line,date:"
+msgid "Date"
+msgstr "日期"
+
+msgctxt "field:timesheet.line,description:"
+msgid "Description"
+msgstr "描述"
+
+msgctxt "field:timesheet.line,duration:"
+msgid "Duration"
+msgstr "持续时间"
+
+msgctxt "field:timesheet.line,employee:"
+msgid "Employee"
+msgstr "雇员"
+
+msgctxt "field:timesheet.line,uuid:"
+msgid "UUID"
+msgstr "UUID"
+
+msgctxt "field:timesheet.line,work:"
+msgid "Work"
+msgstr "工作"
+
+msgctxt "field:timesheet.line.enter.start,date:"
+msgid "Date"
+msgstr "日期"
+
+msgctxt "field:timesheet.line.enter.start,employee:"
+msgid "Employee"
+msgstr "雇员"
+
+msgctxt "field:timesheet.work,company:"
+msgid "Company"
+msgstr "公司"
+
+msgctxt "field:timesheet.work,duration:"
+msgid "Timesheet Duration"
+msgstr "时间表持续时间"
+
+msgctxt "field:timesheet.work,name:"
+msgid "Name"
+msgstr "名称"
+
+msgctxt "field:timesheet.work,origin:"
+msgid "Origin"
+msgstr "来源"
+
+msgctxt "field:timesheet.work,timesheet_end_date:"
+msgid "Timesheet End"
+msgstr "时间表结束"
+
+msgctxt "field:timesheet.work,timesheet_lines:"
+msgid "Timesheet Lines"
+msgstr "时间表明细"
+
+msgctxt "field:timesheet.work,timesheet_start_date:"
+msgid "Timesheet Start"
+msgstr "时间表开始"
+
+msgctxt "field:timesheet.work,work:"
+msgid "Work"
+msgstr "工作"
+
+msgctxt "field:timesheet.work.context,from_date:"
+msgid "From Date"
+msgstr "从日期"
+
+msgctxt "field:timesheet.work.context,to_date:"
+msgid "To Date"
+msgstr "到日期"
+
+msgctxt "help:timesheet.line,company:"
+msgid "The company on which the time is spent."
+msgstr "消耗时间的公司."
+
+msgctxt "help:timesheet.line,date:"
+msgid "When the time is spent."
+msgstr "时间消耗的时候."
+
+msgctxt "help:timesheet.line,description:"
+msgid "Additional description of the work done."
+msgstr "完成工作的附加说明."
+
+msgctxt "help:timesheet.line,employee:"
+msgid "The employee who spends the time."
+msgstr "消耗时间的雇员."
+
+msgctxt "help:timesheet.line,work:"
+msgid "The work on which the time is spent."
+msgstr "消耗时间的工作."
+
+msgctxt "help:timesheet.line.enter.start,date:"
+msgid "When the time is spent."
+msgstr "消耗时间的时候."
+
+msgctxt "help:timesheet.line.enter.start,employee:"
+msgid "The employee who spends the time."
+msgstr "消耗时间的雇员."
+
+msgctxt "help:timesheet.work,company:"
+msgid "Make the work belong to the company."
+msgstr "让这项工作属于公司."
+
+msgctxt "help:timesheet.work,duration:"
+msgid "Total time spent on this work."
+msgstr "消耗在这项工作上的总时间."
+
+msgctxt "help:timesheet.work,name:"
+msgid "The main identifier of the work."
+msgstr "工作的主标识符."
+
+msgctxt "help:timesheet.work,origin:"
+msgid "Use to relate the time spent to other records."
+msgstr "用于将所花费的时间与其他记录相关联."
+
+msgctxt "help:timesheet.work,timesheet_end_date:"
+msgid "Restrict adding lines after the date."
+msgstr "限制只允许在日期之后添加行."
+
+msgctxt "help:timesheet.work,timesheet_lines:"
+msgid "Spend time on this work."
+msgstr "此项工作消耗的时间."
+
+msgctxt "help:timesheet.work,timesheet_start_date:"
+msgid "Restrict adding lines before the date."
+msgstr "限制只允许在日期之前添加行."
+
+msgctxt "help:timesheet.work.context,from_date:"
+msgid "Do not take into account lines before the date."
+msgstr "不考虑日期之前的行."
+
+msgctxt "help:timesheet.work.context,to_date:"
+msgid "Do not take into account lines after the date."
+msgstr "不考虑日期之后的行."
+
+msgctxt "model:ir.action,name:act_hours_employee_form"
+msgid "Hours per Employee"
+msgstr "雇员工作小时数"
+
+msgctxt "model:ir.action,name:act_hours_employee_monthly_form"
+msgid "Hours per Employee per Month"
+msgstr "雇员月工作小时数"
+
+msgctxt "model:ir.action,name:act_hours_employee_weekly_form"
+msgid "Hours per Employee per Week"
+msgstr "雇员周工作小时数"
+
+msgctxt "model:ir.action,name:act_line_enter"
+msgid "Enter Timesheet"
+msgstr "输入时间表"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_line_form"
+msgid "Lines"
+msgstr "输入行"
+
+msgctxt "model:ir.action,name:act_line_form_work"
+msgid "Timesheet Lines"
+msgstr "时间表明细"
+
+msgctxt "model:ir.action,name:act_work_list"
+msgid "Works"
+msgstr "工作"
+
+msgctxt "model:ir.action,name:act_work_report"
+msgid "Works"
+msgstr "工作"
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_all"
+msgid "All"
+msgstr ""
+
+msgctxt "model:ir.action.act_window.domain,name:act_work_list_domain_open"
+msgid "Open"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_line_duration_positive"
+msgid "The duration of line \"%(line)s\" must be positive."
+msgstr "持续时间明细 \"%(line)s\" 必须为正."
+
+msgctxt "model:ir.message,text:msg_line_uuid_unique"
+msgid "The UUID of timesheet line must be unique."
+msgstr "时间表的 UUID 不能重复."
+
+msgctxt "model:ir.message,text:msg_work_company_different_origin"
+msgid "The companies associated with work \"%(work)s\" and its origin differ."
+msgstr "工作\"%(work)s\"关联的公司与其来源关联的公司不一致."
+
+msgctxt "model:ir.message,text:msg_work_origin_unique_company"
+msgid "Work origin must be unique by company."
+msgstr "工作来源必须按公司唯一."
+
+msgctxt "model:ir.rule.group,name:rule_group_hours_employee_admin"
+msgid "Any employee hours"
+msgstr "任意雇员工时"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_hours_employees"
+msgid "Own employee hours"
+msgstr "自有雇员工时"
+
+msgctxt "model:ir.rule.group,name:rule_group_line"
+msgid "Own timesheet line"
+msgstr "自有雇员时间表明细"
+
+msgctxt "model:ir.rule.group,name:rule_group_line_admin"
+msgid "Any timesheet line"
+msgstr "任意雇员工时间表明细"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_line_companies"
+msgid "User in companies"
+msgstr "公司中的用户"
+
+#, fuzzy
+msgctxt "model:ir.rule.group,name:rule_group_work_companies"
+msgid "User in companies"
+msgstr "公司中的用户"
+
+msgctxt "model:ir.ui.menu,name:menu_configuration"
+msgid "Configuration"
+msgstr "设置"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee"
+msgid "Hours per Employee"
+msgstr "雇员工作小时数"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_monthly"
+msgid "Hours per Employee per Month"
+msgstr "雇员月工作小时数"
+
+msgctxt "model:ir.ui.menu,name:menu_hours_employee_open_weekly"
+msgid "Hours per Employee per Week"
+msgstr "雇员周工作小时数"
+
+msgctxt "model:ir.ui.menu,name:menu_line_enter"
+msgid "Enter Timesheet"
+msgstr "输入时间表"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_line_form"
+msgid "Lines"
+msgstr "输入行"
+
+msgctxt "model:ir.ui.menu,name:menu_reporting"
+msgid "Reporting"
+msgstr "报表"
+
+msgctxt "model:ir.ui.menu,name:menu_timesheet"
+msgid "Timesheet"
+msgstr "时间表"
+
+msgctxt "model:ir.ui.menu,name:menu_work_list"
+msgid "Works"
+msgstr "工作"
+
+msgctxt "model:ir.ui.menu,name:menu_work_report"
+msgid "Works"
+msgstr "工作"
+
+msgctxt "model:res.group,name:group_timesheet_admin"
+msgid "Timesheet Administration"
+msgstr "时间表管理"
+
+msgctxt "model:timesheet.hours_employee,name:"
+msgid "Hours per Employee"
+msgstr "雇员工作小时数"
+
+msgctxt "model:timesheet.hours_employee.context,name:"
+msgid "Hours per Employee Context"
+msgstr "雇员的工作时间"
+
+msgctxt "model:timesheet.hours_employee_monthly,name:"
+msgid "Hours per Employee per Month"
+msgstr "雇员月工作小时数"
+
+msgctxt "model:timesheet.hours_employee_weekly,name:"
+msgid "Hours per Employee per Week"
+msgstr "雇员周工作小时数"
+
+msgctxt "model:timesheet.line,name:"
+msgid "Timesheet Line"
+msgstr "时间表明细"
+
+msgctxt "model:timesheet.line.enter.start,name:"
+msgid "Enter Lines"
+msgstr "输入行"
+
+msgctxt "model:timesheet.work,name:"
+msgid "Work"
+msgstr "工作"
+
+msgctxt "model:timesheet.work.context,name:"
+msgid "Work Context"
+msgstr "工作环境"
+
+msgctxt "selection:res.user.application,application:"
+msgid "Timesheet"
+msgstr "时间表"
+
+msgctxt "view:timesheet.hours_employee:"
+msgid "Duration"
+msgstr "持续时间"
+
+msgctxt "view:timesheet.line:"
+msgid "Duration"
+msgstr "持续时间"
+
+msgctxt "wizard_button:timesheet.line.enter,start,end:"
+msgid "Cancel"
+msgstr "取消"
+
+msgctxt "wizard_button:timesheet.line.enter,start,enter:"
+msgid "Enter"
+msgstr "输入"
diff --git a/modules/timesheet/message.xml b/modules/timesheet/message.xml
new file mode 100644
index 0000000000..d015f75e0a
--- /dev/null
+++ b/modules/timesheet/message.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ The companies associated with work "%(work)s" and its origin differ.
+
+
+ The duration of line "%(line)s" must be positive.
+
+
+ Work origin must be unique by company.
+
+
+ The UUID of timesheet line must be unique.
+
+
+
diff --git a/modules/timesheet/routes.py b/modules/timesheet/routes.py
new file mode 100644
index 0000000000..1042158b2d
--- /dev/null
+++ b/modules/timesheet/routes.py
@@ -0,0 +1,136 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+
+from werkzeug.exceptions import abort
+from werkzeug.wrappers import Response
+
+from trytond.transaction import Transaction
+from trytond.wsgi import app
+from trytond.protocols.wrappers import (
+ with_pool, with_transaction, user_application, allow_null_origin)
+
+timesheet_application = user_application('timesheet')
+
+
+@app.route('//timesheet/employees', methods=['GET'])
+@allow_null_origin
+@with_pool
+@with_transaction()
+@timesheet_application
+def timesheet_employees(request, pool):
+ User = pool.get('res.user')
+ user = User(Transaction().user)
+ return [{'id': e.id, 'name': e.rec_name} for e in user.employees]
+
+
+@app.route('//timesheet/employee//works',
+ methods=['GET'])
+@allow_null_origin
+@with_pool
+@with_transaction()
+@timesheet_application
+def timesheet_works(request, pool, employee):
+ Work = pool.get('timesheet.work')
+ Employee = pool.get('company.employee')
+ employee = Employee(employee)
+ with Transaction().set_context(
+ company=employee.company.id, employee=employee.id):
+ works = Work.search([
+ ('company', '=', employee.company.id),
+ ])
+ return sorted(({
+ 'id': w.id,
+ 'name': w.rec_name,
+ 'start': (w.timesheet_start_date.strftime('%Y-%m-%d')
+ if w.timesheet_start_date else None),
+ 'end': (w.timesheet_end_date.strftime('%Y-%m-%d')
+ if w.timesheet_end_date else None),
+ } for w in works),
+ key=lambda w: w['name'].lower())
+
+
+@app.route('//timesheet/employee//lines/',
+ methods=['GET'])
+@allow_null_origin
+@with_pool
+@with_transaction()
+@timesheet_application
+def timesheet_lines(request, pool, employee, date):
+ User = pool.get('res.user')
+ Employee = pool.get('company.employee')
+ Line = pool.get('timesheet.line')
+
+ employee = Employee(employee)
+ date = datetime.datetime.strptime(date, '%Y-%m-%d').date()
+ user = User(Transaction().user)
+ if employee not in user.employees:
+ abort(403)
+
+ with Transaction().set_context(
+ company=employee.company.id, employee=employee.id):
+ lines = Line.search([
+ ('employee', '=', employee.id),
+ ('date', '=', date),
+ ],
+ order=[('id', 'ASC')])
+ return [l.to_json() for l in lines]
+
+
+@app.route('//timesheet/line/',
+ methods=['PUT', 'DELETE'])
+@app.route('//timesheet/line', methods=['POST'])
+@allow_null_origin
+@with_pool
+@with_transaction()
+@timesheet_application
+def timesheet(request, pool, line=None):
+ Line = pool.get('timesheet.line')
+ User = pool.get('res.user')
+ Employee = pool.get('company.employee')
+ if request.method in {'POST', 'PUT'}:
+ data = request.parsed_data.copy()
+
+ if not line and data.get('uuid'):
+ lines = Line.search([
+ ('uuid', '=', data['uuid']),
+ ])
+ if lines:
+ line, = lines
+
+ if 'employee' in data:
+ employee = Employee(data['employee'])
+ data['company'] = employee.company.id
+ else:
+ employee = User(Transaction().user).employee
+ if 'date' in data:
+ data['date'] = datetime.datetime.strptime(
+ data['date'], '%Y-%m-%d').date()
+ if 'duration' in data:
+ data['duration'] = datetime.timedelta(
+ seconds=data['duration'])
+
+ for extra in set(data) - set(Line._fields):
+ del data[extra]
+
+ with Transaction().set_context(
+ company=employee.company.id, employee=employee.id):
+ if not line:
+ line, = Line.create([data])
+ else:
+ lines = Line.search([('id', '=', int(line))])
+ if not lines:
+ return Response(None, 204)
+ line, = lines
+ Line.write(lines, data)
+ return line.to_json()
+ else:
+ with Transaction().set_user(0):
+ lines = Line.search([('id', '=', line)])
+ if lines:
+ line, = lines
+ with Transaction().set_context(
+ company=line.company.id, employee=line.employee.id):
+ lines = Line.search([('id', '=', line.id)])
+ Line.delete(lines)
+ return Response(None, 204)
diff --git a/modules/timesheet/setup.py b/modules/timesheet/setup.py
new file mode 100755
index 0000000000..8916167117
--- /dev/null
+++ b/modules/timesheet/setup.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname, slice=None):
+ content = io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+ if slice:
+ content = '\n'.join(content.splitlines()[slice])
+ return content
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_timesheet'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = ['python-sql', 'werkzeug']
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module with timesheets',
+ long_description=read('README.rst', slice=slice(0, -5)),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/timesheet',
+ },
+ keywords='tryton timesheet',
+ package_dir={'trytond.modules.timesheet': '.'},
+ packages=(
+ ['trytond.modules.timesheet']
+ + ['trytond.modules.timesheet.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.timesheet': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', 'icons/*.svg']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ timesheet = trytond.modules.timesheet
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ )
diff --git a/modules/timesheet/tests/__init__.py b/modules/timesheet/tests/__init__.py
new file mode 100644
index 0000000000..3fc9d40489
--- /dev/null
+++ b/modules/timesheet/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.timesheet.tests.test_timesheet import suite
+except ImportError:
+ from .test_timesheet import suite
+
+__all__ = ['suite']
diff --git a/modules/timesheet/tests/test_timesheet.py b/modules/timesheet/tests/test_timesheet.py
new file mode 100644
index 0000000000..885a677fb8
--- /dev/null
+++ b/modules/timesheet/tests/test_timesheet.py
@@ -0,0 +1,19 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import unittest
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase
+
+from trytond.modules.company.tests import CompanyTestMixin
+
+
+class TimesheetTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test Timesheet module'
+ module = 'timesheet'
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ TimesheetTestCase))
+ return suite
diff --git a/modules/timesheet/timesheet.xml b/modules/timesheet/timesheet.xml
new file mode 100644
index 0000000000..9fc2b9f481
--- /dev/null
+++ b/modules/timesheet/timesheet.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Timesheet Administration
+
+
+
+
+
+
+
+ tryton-timesheet
+ icons/tryton-timesheet.svg
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/tox.ini b/modules/timesheet/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/timesheet/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/timesheet/tryton.cfg b/modules/timesheet/tryton.cfg
new file mode 100644
index 0000000000..15c721efd7
--- /dev/null
+++ b/modules/timesheet/tryton.cfg
@@ -0,0 +1,12 @@
+[tryton]
+version=6.0.3
+depends:
+ company
+ company_work_time
+ ir
+ res
+xml:
+ timesheet.xml
+ work.xml
+ line.xml
+ message.xml
diff --git a/modules/timesheet/user.py b/modules/timesheet/user.py
new file mode 100644
index 0000000000..7a5f143f6f
--- /dev/null
+++ b/modules/timesheet/user.py
@@ -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.pool import PoolMeta
+
+
+class UserApplication(metaclass=PoolMeta):
+ __name__ = 'res.user.application'
+
+ @classmethod
+ def __setup__(cls):
+ super(UserApplication, cls).__setup__()
+ cls.application.selection.append(('timesheet', 'Timesheet'))
diff --git a/modules/timesheet/view/hours_employee_context_form.xml b/modules/timesheet/view/hours_employee_context_form.xml
new file mode 100644
index 0000000000..029f1a04e5
--- /dev/null
+++ b/modules/timesheet/view/hours_employee_context_form.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/modules/timesheet/view/hours_employee_graph.xml b/modules/timesheet/view/hours_employee_graph.xml
new file mode 100644
index 0000000000..dceb3c3888
--- /dev/null
+++ b/modules/timesheet/view/hours_employee_graph.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/view/hours_employee_monthly_tree.xml b/modules/timesheet/view/hours_employee_monthly_tree.xml
new file mode 100644
index 0000000000..5d7a62e9f3
--- /dev/null
+++ b/modules/timesheet/view/hours_employee_monthly_tree.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/view/hours_employee_tree.xml b/modules/timesheet/view/hours_employee_tree.xml
new file mode 100644
index 0000000000..4b2f42967a
--- /dev/null
+++ b/modules/timesheet/view/hours_employee_tree.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/modules/timesheet/view/hours_employee_weekly_tree.xml b/modules/timesheet/view/hours_employee_weekly_tree.xml
new file mode 100644
index 0000000000..aaba6eb388
--- /dev/null
+++ b/modules/timesheet/view/hours_employee_weekly_tree.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/view/line_enter_start_form.xml b/modules/timesheet/view/line_enter_start_form.xml
new file mode 100644
index 0000000000..b0702056f5
--- /dev/null
+++ b/modules/timesheet/view/line_enter_start_form.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/modules/timesheet/view/line_form.xml b/modules/timesheet/view/line_form.xml
new file mode 100644
index 0000000000..5175373a7b
--- /dev/null
+++ b/modules/timesheet/view/line_form.xml
@@ -0,0 +1,17 @@
+
+
+
diff --git a/modules/timesheet/view/line_tree.xml b/modules/timesheet/view/line_tree.xml
new file mode 100644
index 0000000000..be7545c2b6
--- /dev/null
+++ b/modules/timesheet/view/line_tree.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/view/work_context_form.xml b/modules/timesheet/view/work_context_form.xml
new file mode 100644
index 0000000000..39f1822f77
--- /dev/null
+++ b/modules/timesheet/view/work_context_form.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/modules/timesheet/view/work_form.xml b/modules/timesheet/view/work_form.xml
new file mode 100644
index 0000000000..8577da93a3
--- /dev/null
+++ b/modules/timesheet/view/work_form.xml
@@ -0,0 +1,19 @@
+
+
+
diff --git a/modules/timesheet/view/work_graph.xml b/modules/timesheet/view/work_graph.xml
new file mode 100644
index 0000000000..8dd66a2fb3
--- /dev/null
+++ b/modules/timesheet/view/work_graph.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/view/work_list.xml b/modules/timesheet/view/work_list.xml
new file mode 100644
index 0000000000..d98a8694c2
--- /dev/null
+++ b/modules/timesheet/view/work_list.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet/view/work_list_report.xml b/modules/timesheet/view/work_list_report.xml
new file mode 100644
index 0000000000..2f0fff327e
--- /dev/null
+++ b/modules/timesheet/view/work_list_report.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/timesheet/work.py b/modules/timesheet/work.py
new file mode 100644
index 0000000000..03012070d7
--- /dev/null
+++ b/modules/timesheet/work.py
@@ -0,0 +1,224 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import datetime
+
+from sql import Literal
+from sql.aggregate import Sum
+
+from trytond.i18n import gettext
+from trytond.model import ModelView, ModelSQL, ModelStorage, fields, Unique
+from trytond.pyson import Bool, Eval, If
+from trytond.transaction import Transaction
+from trytond.pool import Pool
+from trytond.tools import reduce_ids, grouped_slice
+
+from .exceptions import CompanyValidationError
+
+
+class Work(ModelSQL, ModelView):
+ 'Work'
+ __name__ = 'timesheet.work'
+ name = fields.Char('Name',
+ states={
+ 'invisible': Bool(Eval('origin')),
+ 'required': ~Eval('origin'),
+ },
+ depends=['origin'],
+ help="The main identifier of the work.")
+ origin = fields.Reference('Origin', selection='get_origin',
+ states={
+ 'invisible': Bool(Eval('name')),
+ 'required': ~Eval('name'),
+ },
+ depends=['name'],
+ help="Use to relate the time spent to other records.")
+ duration = fields.Function(fields.TimeDelta('Timesheet Duration',
+ 'company_work_time', help="Total time spent on this work."),
+ 'get_duration')
+ timesheet_start_date = fields.Date('Timesheet Start',
+ domain=[
+ If(Eval('timesheet_start_date') & Eval('timesheet_end_date'),
+ ('timesheet_start_date', '<=', Eval('timesheet_end_date')),
+ ()),
+ ],
+ depends=['timesheet_end_date'],
+ help="Restrict adding lines before the date.")
+ timesheet_end_date = fields.Date('Timesheet End',
+ domain=[
+ If(Eval('timesheet_start_date') & Eval('timesheet_end_date'),
+ ('timesheet_end_date', '>=', Eval('timesheet_start_date')),
+ ()),
+ ],
+ depends=['timesheet_start_date'],
+ help="Restrict adding lines after the date.")
+ company = fields.Many2One('company.company', 'Company', required=True,
+ select=True, help="Make the work belong to the company.")
+ timesheet_lines = fields.One2Many('timesheet.line', 'work',
+ 'Timesheet Lines',
+ help="Spend time on this work.")
+ # Self referring field to use for aggregation in graph view
+ work = fields.Function(fields.Many2One('timesheet.work', 'Work'),
+ 'get_work')
+
+ @classmethod
+ def __setup__(cls):
+ super(Work, cls).__setup__()
+ t = cls.__table__()
+ cls._sql_constraints += [
+ ('origin_unique', Unique(t, t.origin, t.company),
+ 'timesheet.msg_work_origin_unique_company'),
+ ]
+
+ @classmethod
+ def __register__(cls, module_name):
+ table_h = cls.__table_handler__(module_name)
+ table = cls.__table__()
+ cursor = Transaction().connection.cursor()
+
+ super(Work, cls).__register__(module_name)
+
+ # Migration from 4.0: remove required on name
+ table_h.not_null_action('name', 'remove')
+
+ # Migration from 4.0: remove parent, left and right
+ if table_h.column_exist('parent'):
+ id2name = {}
+ id2parent = {}
+ cursor.execute(*table.select(
+ table.id, table.parent, table.name))
+ for id_, parent, name in cursor:
+ id2name[id_] = name
+ id2parent[id_] = parent
+
+ for id_, name in id2name.items():
+ parent = id2parent[id_]
+ while parent:
+ name = '%s\\%s' % (id2name[parent], name)
+ parent = id2parent[parent]
+ cursor.execute(*table.update(
+ [table.name], [name],
+ where=table.id == id_))
+ table_h.drop_column('parent')
+ table_h.drop_column('left')
+ table_h.drop_column('right')
+
+ # Migration from 4.0: remove timesheet_available
+ if table_h.column_exist('timesheet_available'):
+ cursor.execute(*table.delete(
+ where=table.timesheet_available == Literal(False)))
+ table_h.drop_column('timesheet_available')
+
+ @staticmethod
+ def default_company():
+ return Transaction().context.get('company')
+
+ @classmethod
+ def _get_origin(cls):
+ 'Return list of Model names for origin Reference'
+ return []
+
+ @classmethod
+ def get_origin(cls):
+ Model = Pool().get('ir.model')
+ get_name = Model.get_name
+ models = cls._get_origin()
+ return [('', '')] + [(m, get_name(m)) for m in models]
+
+ @classmethod
+ def get_duration(cls, works, name):
+ pool = Pool()
+ Line = pool.get('timesheet.line')
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ context = transaction.context
+
+ table_w = cls.__table__()
+ line = Line.__table__()
+ ids = [w.id for w in works]
+ durations = dict.fromkeys(ids, None)
+ where = Literal(True)
+ if context.get('from_date'):
+ where &= line.date >= context['from_date']
+ if context.get('to_date'):
+ where &= line.date <= context['to_date']
+ if context.get('employees'):
+ where &= line.employee.in_(context['employees'])
+
+ query_table = table_w.join(line, 'LEFT',
+ condition=line.work == table_w.id)
+
+ for sub_ids in grouped_slice(ids):
+ red_sql = reduce_ids(table_w.id, sub_ids)
+ cursor.execute(*query_table.select(table_w.id, Sum(line.duration),
+ where=red_sql & where,
+ group_by=table_w.id))
+ for work_id, duration in cursor:
+ # SQLite uses float for SUM
+ if duration and not isinstance(duration, datetime.timedelta):
+ duration = datetime.timedelta(seconds=duration)
+ durations[work_id] = duration
+ return durations
+
+ def get_work(self, name):
+ return self.id
+
+ def get_rec_name(self, name):
+ if isinstance(self.origin, ModelStorage):
+ return self.origin.rec_name
+ else:
+ return self.name
+
+ @classmethod
+ def search_rec_name(cls, name, clause):
+ if clause[1].startswith('!') or clause[1].startswith('not '):
+ bool_op = 'AND'
+ else:
+ bool_op = 'OR'
+ return [bool_op,
+ ('name',) + tuple(clause[1:]),
+ ] + [
+ ('origin.rec_name',) + tuple(clause[1:]) + (origin,)
+ for origin in cls._get_origin()]
+
+ @classmethod
+ def copy(cls, works, default=None):
+ if default is None:
+ default = {}
+ else:
+ default = default.copy()
+ default.setdefault('timesheet_lines', None)
+ return super(Work, cls).copy(works, default=default)
+
+ @classmethod
+ def validate(cls, works):
+ super(Work, cls).validate(works)
+ for work in works:
+ if work.origin and not work._validate_company():
+ raise CompanyValidationError(
+ gettext('timesheet.msg_work_company_different_origin',
+ work=work.rec_name))
+
+ def _validate_company(self):
+ return True
+
+ @classmethod
+ def search_global(cls, text):
+ for record, rec_name, icon in super(Work, cls).search_global(text):
+ icon = icon or 'tryton-clock'
+ yield record, rec_name, icon
+
+ @property
+ def hours(self):
+ if not self.duration:
+ return 0
+ return self.duration.total_seconds() / 60 / 60
+
+
+class WorkContext(ModelView):
+ 'Work Context'
+ __name__ = 'timesheet.work.context'
+ from_date = fields.Date('From Date',
+ help="Do not take into account lines before the date.")
+ to_date = fields.Date('To Date',
+ help="Do not take into account lines after the date.")
diff --git a/modules/timesheet/work.xml b/modules/timesheet/work.xml
new file mode 100644
index 0000000000..5c8a306437
--- /dev/null
+++ b/modules/timesheet/work.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+ timesheet.work
+ form
+ work_form
+
+
+ timesheet.work
+ tree
+
+ work_list
+
+
+ timesheet.work
+ tree
+
+ work_list_report
+
+
+ timesheet.work
+ graph
+ work_graph
+
+
+
+ Works
+ timesheet.work
+
+
+
+
+
+
+
+
+
+
+
+
+ Open
+
+
+
+
+
+
+ All
+
+
+
+
+
+
+
+ Works
+ timesheet.work
+ timesheet.work.context
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ User in companies
+
+
+
+
+
+
+
+
+
+ timesheet.work.context
+ form
+ work_context_form
+
+
+
diff --git a/modules/timesheet_cost/.drone.yml b/modules/timesheet_cost/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/timesheet_cost/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/timesheet_cost/.flake8 b/modules/timesheet_cost/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/timesheet_cost/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/timesheet_cost/CHANGELOG b/modules/timesheet_cost/CHANGELOG
new file mode 100644
index 0000000000..badb1e992f
--- /dev/null
+++ b/modules/timesheet_cost/CHANGELOG
@@ -0,0 +1,51 @@
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+* Store cost price on timesheet line
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+
+Version 3.0.0 - 2013-10-21
+* Initial release
diff --git a/modules/timesheet_cost/COPYRIGHT b/modules/timesheet_cost/COPYRIGHT
new file mode 100644
index 0000000000..15fabb078d
--- /dev/null
+++ b/modules/timesheet_cost/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2008-2021 Cédric Krier.
+Copyright (C) 2008-2021 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/timesheet_cost/LICENSE b/modules/timesheet_cost/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/timesheet_cost/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/timesheet_cost/MANIFEST.in b/modules/timesheet_cost/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/timesheet_cost/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/timesheet_cost/README.rst b/modules/timesheet_cost/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/timesheet_cost/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/timesheet_cost/__init__.py b/modules/timesheet_cost/__init__.py
new file mode 100644
index 0000000000..e1f310f4f4
--- /dev/null
+++ b/modules/timesheet_cost/__init__.py
@@ -0,0 +1,14 @@
+# 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 company
+from . import timesheet
+
+
+def register():
+ Pool.register(
+ company.Employee,
+ company.EmployeeCostPrice,
+ timesheet.Line,
+ module='timesheet_cost', type_='model')
diff --git a/modules/timesheet_cost/company.py b/modules/timesheet_cost/company.py
new file mode 100644
index 0000000000..d9be692945
--- /dev/null
+++ b/modules/timesheet_cost/company.py
@@ -0,0 +1,119 @@
+# 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 decimal import Decimal
+from trytond.model import ModelView, ModelSQL, fields, Unique
+from trytond.cache import Cache
+from trytond.transaction import Transaction
+from trytond.pool import Pool, PoolMeta
+from trytond.config import config
+
+__all__ = ['price_digits']
+
+price_digits = (16, config.getint(
+ 'timesheet_cost', 'price_decimal', default=4))
+
+
+class Employee(metaclass=PoolMeta):
+ __name__ = 'company.employee'
+ cost_price = fields.Function(fields.Numeric('Cost Price',
+ digits=price_digits,
+ help="Hourly cost price for this Employee."), 'get_cost_price')
+ cost_prices = fields.One2Many('company.employee_cost_price', 'employee',
+ 'Cost Prices', help="List of hourly cost price over time.")
+ _cost_prices_cache = Cache('company_employee.cost_prices')
+
+ def get_cost_price(self, name):
+ '''
+ Return the cost price at the date given in the context or the
+ current date
+ '''
+ ctx_date = Transaction().context.get('date', None)
+ return self.compute_cost_price(ctx_date)
+
+ def get_employee_costs(self):
+ "Return a sorted list by date of start date and cost_price"
+ pool = Pool()
+ CostPrice = pool.get('company.employee_cost_price')
+ # Get from cache employee costs or fetch them from the db
+ employee_costs = self._cost_prices_cache.get(self.id)
+ if employee_costs is None:
+ cost_prices = CostPrice.search([
+ ('employee', '=', self.id),
+ ], order=[('date', 'ASC')])
+
+ employee_costs = []
+ for cost_price in cost_prices:
+ employee_costs.append(
+ (cost_price.date, cost_price.cost_price))
+ self._cost_prices_cache.set(self.id, employee_costs)
+ return employee_costs
+
+ def compute_cost_price(self, date=None):
+ "Return the cost price at the given date"
+ pool = Pool()
+ Date = pool.get('ir.date')
+
+ employee_costs = self.get_employee_costs()
+
+ if date is None:
+ date = Date.today()
+ # compute the cost price for the given date
+ cost = 0
+ if employee_costs and date >= employee_costs[0][0]:
+ for edate, ecost in employee_costs:
+ if date >= edate:
+ cost = ecost
+ else:
+ break
+ return cost
+
+
+class EmployeeCostPrice(ModelSQL, ModelView):
+ 'Employee Cost Price'
+ __name__ = 'company.employee_cost_price'
+ date = fields.Date('Date', required=True, select=True)
+ cost_price = fields.Numeric('Cost Price',
+ digits=price_digits, required=True, help="Hourly cost price.")
+ employee = fields.Many2One('company.employee', 'Employee')
+
+ @classmethod
+ def __setup__(cls):
+ super(EmployeeCostPrice, cls).__setup__()
+ t = cls.__table__()
+ cls._sql_constraints = [
+ ('employee_date_cost_price_uniq',
+ Unique(t, t.employee, t.date, t.cost_price),
+ 'timesheet_cost.msg_employee_unique_cost_price_date'),
+ ]
+ cls._order.insert(0, ('date', 'DESC'))
+
+ @staticmethod
+ def default_cost_price():
+ return Decimal(0)
+
+ @staticmethod
+ def default_date():
+ Date = Pool().get('ir.date')
+ return Date.today()
+
+ def get_rec_name(self, name):
+ return str(self.date)
+
+ @classmethod
+ def delete(cls, prices):
+ Employee = Pool().get('company.employee')
+ super(EmployeeCostPrice, cls).delete(prices)
+ Employee._cost_prices_cache.clear()
+
+ @classmethod
+ def create(cls, vlist):
+ Employee = Pool().get('company.employee')
+ prices = super(EmployeeCostPrice, cls).create(vlist)
+ Employee._cost_prices_cache.clear()
+ return prices
+
+ @classmethod
+ def write(cls, *args):
+ Employee = Pool().get('company.employee')
+ super(EmployeeCostPrice, cls).write(*args)
+ Employee._cost_prices_cache.clear()
diff --git a/modules/timesheet_cost/company.xml b/modules/timesheet_cost/company.xml
new file mode 100644
index 0000000000..b0eb21e4e0
--- /dev/null
+++ b/modules/timesheet_cost/company.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+ company.employee_cost_price
+ form
+ employee_cost_price_form
+
+
+
+ company.employee_cost_price
+ tree
+ employee_cost_price_tree
+
+
+
+ company.employee
+
+ employee_form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet_cost/doc/index.rst b/modules/timesheet_cost/doc/index.rst
new file mode 100644
index 0000000000..2a19ea1326
--- /dev/null
+++ b/modules/timesheet_cost/doc/index.rst
@@ -0,0 +1,4 @@
+Timesheet Cost Module
+#####################
+
+The timesheet cost module adds cost price per employee.
diff --git a/modules/timesheet_cost/locale/bg.po b/modules/timesheet_cost/locale/bg.po
new file mode 100644
index 0000000000..75ca04e00c
--- /dev/null
+++ b/modules/timesheet_cost/locale/bg.po
@@ -0,0 +1,54 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Фабрична цена"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Себестойност"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Фабрична цена"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Служител"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Фабрична цена"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Себестойност за час за този служител"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Списък на себестойност за час през времето"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Себестойност за час"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Себестойност на клиент"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Разход"
diff --git a/modules/timesheet_cost/locale/ca.po b/modules/timesheet_cost/locale/ca.po
new file mode 100644
index 0000000000..1d781df650
--- /dev/null
+++ b/modules/timesheet_cost/locale/ca.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Preu de cost"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Preus de cost"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Preu de cost"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Empleat"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Preu de cost"
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Preu de cost per hora per a aquest empleat."
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Llista de preus de cost per hora a través del temps."
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Preu de cost per hora."
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Preu de cost de l'empleat"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Un empleat només pot tenir un preu de cost per data."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Cost"
diff --git a/modules/timesheet_cost/locale/cs.po b/modules/timesheet_cost/locale/cs.po
new file mode 100644
index 0000000000..4116d879a5
--- /dev/null
+++ b/modules/timesheet_cost/locale/cs.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr ""
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr ""
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr ""
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr ""
diff --git a/modules/timesheet_cost/locale/de.po b/modules/timesheet_cost/locale/de.po
new file mode 100644
index 0000000000..cea043fb7e
--- /dev/null
+++ b/modules/timesheet_cost/locale/de.po
@@ -0,0 +1,53 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Einstandspreis"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Einstandspreise"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Einstandspreis"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Mitarbeiter"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Einstandspreis"
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Einstandspreis pro Stunde für diesen Mitarbeiter."
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Entwicklung des Einstandspreises pro Stunde."
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Einstandspreis pro Stunde."
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Mitarbeiter Einstandspreis"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+"Es kann nur ein Einstandspreis pro Datum für einen Mitarbeiter eingetragen "
+"werden."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Kosten"
diff --git a/modules/timesheet_cost/locale/es.po b/modules/timesheet_cost/locale/es.po
new file mode 100644
index 0000000000..53d87f783e
--- /dev/null
+++ b/modules/timesheet_cost/locale/es.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Precio de coste"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Precios de coste"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Precio de coste"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Fecha"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Empleado"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Precio de coste"
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Precio de coste por hora para este empleado."
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Lista de precios de coste por hora a lo largo del tiempo."
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Precio de coste por hora."
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Precio de coste del empleado"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Un empleado sólo puede tener un precio de coste por fecha."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Coste"
diff --git a/modules/timesheet_cost/locale/es_419.po b/modules/timesheet_cost/locale/es_419.po
new file mode 100644
index 0000000000..99398c4171
--- /dev/null
+++ b/modules/timesheet_cost/locale/es_419.po
@@ -0,0 +1,54 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Precio de costo"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Precios de costo"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Precio de costo"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Precio de costo"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Precio de costo por hora para este empleado"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Lista de precio de costo por hora de tiempo extra"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Precio de costo por hora"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Precio de costo del empleado"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Costo"
diff --git a/modules/timesheet_cost/locale/et.po b/modules/timesheet_cost/locale/et.po
new file mode 100644
index 0000000000..9b138fc9e0
--- /dev/null
+++ b/modules/timesheet_cost/locale/et.po
@@ -0,0 +1,54 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Ostuhind"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Ostuhinnad"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Ostuhind"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Kuupäev"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Töötaja"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Ostuhind"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Töötaja tunnitasu"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Tunnitasu ületunni hind"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Tunnitasu"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Töötaja tasu"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Töötajal saab olla ainult üks tasu kuupäeva kohta"
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Kulu"
diff --git a/modules/timesheet_cost/locale/fa.po b/modules/timesheet_cost/locale/fa.po
new file mode 100644
index 0000000000..bddcb19466
--- /dev/null
+++ b/modules/timesheet_cost/locale/fa.po
@@ -0,0 +1,54 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "مبلغ هزینه"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "مبالغ هزینه"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "مبلغ هزینه"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "تاریخ"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "کارمند"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "مبلغ هزینه"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "مبلغ هزینه ساعتی برای این کارمند"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "لیست مبلغ هزینه ساعتی اضافه کار"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "مبلغ هزینه ساعتی"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "مبلغ هزینه کارمند"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "در هر تاریخ ،یک کارمند تنها می تواند یک مبلغ هزینه داشته باشد."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "هزینه"
diff --git a/modules/timesheet_cost/locale/fi.po b/modules/timesheet_cost/locale/fi.po
new file mode 100644
index 0000000000..4116d879a5
--- /dev/null
+++ b/modules/timesheet_cost/locale/fi.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr ""
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr ""
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr ""
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr ""
diff --git a/modules/timesheet_cost/locale/fr.po b/modules/timesheet_cost/locale/fr.po
new file mode 100644
index 0000000000..e7e63d7601
--- /dev/null
+++ b/modules/timesheet_cost/locale/fr.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Prix de revient"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Prix de revient"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Prix de revient"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Date"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Employé"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Prix de revient"
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Prix de revient horaire de cet employé."
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Liste du prix de revient horaire au cours du temps."
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Prix de revient horaire."
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Prix de revient de l'employé"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Un employé ne peut avoir qu'un seul prix de revient par date."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Coût"
diff --git a/modules/timesheet_cost/locale/hu.po b/modules/timesheet_cost/locale/hu.po
new file mode 100644
index 0000000000..a92064e1a6
--- /dev/null
+++ b/modules/timesheet_cost/locale/hu.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Költség"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Költség"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Költség"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Dátum"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Alkalmazott"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Költség"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Alkalmazott költsége per óra "
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Lista a költség per óráról"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Költség per óra"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Alkalmazott költsége"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Egy alkalmazottnak dátumonként egy költség írható be."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Költség"
diff --git a/modules/timesheet_cost/locale/id.po b/modules/timesheet_cost/locale/id.po
new file mode 100644
index 0000000000..ff200fa1c5
--- /dev/null
+++ b/modules/timesheet_cost/locale/id.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Tanggal"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Pegawai"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr ""
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr ""
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr ""
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr ""
diff --git a/modules/timesheet_cost/locale/it.po b/modules/timesheet_cost/locale/it.po
new file mode 100644
index 0000000000..0d9d59745e
--- /dev/null
+++ b/modules/timesheet_cost/locale/it.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Costo"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Costi"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Costo"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Dipendente"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Costo"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Costo orario del dipendente"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Serie dei costi orari nel periodo"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "costo orario"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Costo del dipendente"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Un dipendente può avere un costo giornaliero."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Costo"
diff --git a/modules/timesheet_cost/locale/lo.po b/modules/timesheet_cost/locale/lo.po
new file mode 100644
index 0000000000..fea5eea26c
--- /dev/null
+++ b/modules/timesheet_cost/locale/lo.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "ລາຄາຄິດໄລ່"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "ລາຄາຄິດໄລ່"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "ລາຄາຄິດໄລ່"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "ວັນທີ"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "ພະນັກງານ"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "ລາຄາຄິດໄລ່"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "ລາຄາຕໍ່ຊົ່ວໂມງສຳລັບພະນັກງານຜູ້ນີ້"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "ບັນຊີລາຍການຄ່າລ່ວງເວລາ"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "ລາຄາຕໍ່ຊົ່ວໂມງ"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "ມູນຄ່າລາຄາພະນັກງານ"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "ລູກຈ້າງຜູ້ໜຶ່ງສາມາດມີພຽງມູນຄ່າລາຄາດຽວຕໍ່ມື້ເທົ່ານັ້ນ"
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "ມູນຄ່າ"
diff --git a/modules/timesheet_cost/locale/lt.po b/modules/timesheet_cost/locale/lt.po
new file mode 100644
index 0000000000..4116d879a5
--- /dev/null
+++ b/modules/timesheet_cost/locale/lt.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr ""
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr ""
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr ""
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr ""
diff --git a/modules/timesheet_cost/locale/nl.po b/modules/timesheet_cost/locale/nl.po
new file mode 100644
index 0000000000..1a2471dedd
--- /dev/null
+++ b/modules/timesheet_cost/locale/nl.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Kostprijs"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Kostprijs"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Kostprijs"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "datum"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Werknemer"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Kostprijs"
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Kostprijs per uur voor deze werknemer."
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Lijst met kostprijs per uur in de tijd."
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Kostprijs per uur."
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Personeelskostprijs"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Een medewerker kan slechts één kostprijs hebben op datum."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Kosten"
diff --git a/modules/timesheet_cost/locale/pl.po b/modules/timesheet_cost/locale/pl.po
new file mode 100644
index 0000000000..f76a7c0096
--- /dev/null
+++ b/modules/timesheet_cost/locale/pl.po
@@ -0,0 +1,54 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Cena kosztów"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Ceny kosztów"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Cena kosztów"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Pracownik"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Cena kosztów"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Godzinowa cena kosztów"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Godzinowa cena kosztów"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Godzinowa cena kosztów"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Robocza cena kosztów"
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Koszt"
diff --git a/modules/timesheet_cost/locale/pt.po b/modules/timesheet_cost/locale/pt.po
new file mode 100644
index 0000000000..409eaad639
--- /dev/null
+++ b/modules/timesheet_cost/locale/pt.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Preço de custo"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Preços de custo"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Preços de custo"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Empregado"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Preço de custo"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Preço de custo por hora para este empregado."
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Lista de preços de custo por hora ao longo do tempo."
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Preço de custo por hora"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Preço de custo do empregado"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Um empregado somente pode ter um preço de custo por data."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Custo"
diff --git a/modules/timesheet_cost/locale/ro.po b/modules/timesheet_cost/locale/ro.po
new file mode 100644
index 0000000000..332b83e8d1
--- /dev/null
+++ b/modules/timesheet_cost/locale/ro.po
@@ -0,0 +1,51 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Cost"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Costuri"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Cost"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Data"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Angajat"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Cost"
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr ""
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr ""
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr ""
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr ""
diff --git a/modules/timesheet_cost/locale/ru.po b/modules/timesheet_cost/locale/ru.po
new file mode 100644
index 0000000000..21f6baf388
--- /dev/null
+++ b/modules/timesheet_cost/locale/ru.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Себестоимость"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Себестоимость"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Себестоимость"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Дата"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Сотрудник"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Себестоимость"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Часовая себестоимость для этого сотрудника"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Список часовой себестоимости по времени"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Часовая себестоимость"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Себестоимость сотрудника"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "У сотрудника может быть только одна себестоимость за день."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Стоимость"
diff --git a/modules/timesheet_cost/locale/sl.po b/modules/timesheet_cost/locale/sl.po
new file mode 100644
index 0000000000..d502e0fdc0
--- /dev/null
+++ b/modules/timesheet_cost/locale/sl.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Strošek"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Stroški"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Strošek"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Datum"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Zaposlenec"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Strošek"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Strošek na uro za tega zaposlenca"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Seznam stroškov na uro po času"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Strošek na uro"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Strošek zaposlenca"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Zaposlenec ima lahko samo en strošek na datum."
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Strošek"
diff --git a/modules/timesheet_cost/locale/tr.po b/modules/timesheet_cost/locale/tr.po
new file mode 100644
index 0000000000..ec155b1ca6
--- /dev/null
+++ b/modules/timesheet_cost/locale/tr.po
@@ -0,0 +1,55 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr "Maliyet Fiyatı"
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr "Maliyet Fiyatları"
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr "Maliyet Fiyatı"
+
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "Tarih"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr "Çalışan"
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr "Maliyet fiyatı"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr "Bu çalışan için saatlik maliyet fiyatı"
+
+#, fuzzy
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr "Saatlik fazla mesai maliyet fiyatı listesi"
+
+#, fuzzy
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr "Saatlik maliyet fiyatı"
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr "Çalışan Maliyet Fiyatı"
+
+#, fuzzy
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr "Bir çalışan için tarihe göre yalnız bir maliyet fiyatı olabilir"
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr "Maliyet"
diff --git a/modules/timesheet_cost/locale/zh_CN.po b/modules/timesheet_cost/locale/zh_CN.po
new file mode 100644
index 0000000000..b07fba4c2e
--- /dev/null
+++ b/modules/timesheet_cost/locale/zh_CN.po
@@ -0,0 +1,52 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:company.employee,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "field:company.employee,cost_prices:"
+msgid "Cost Prices"
+msgstr ""
+
+msgctxt "field:company.employee_cost_price,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:company.employee_cost_price,date:"
+msgid "Date"
+msgstr "日期格式"
+
+msgctxt "field:company.employee_cost_price,employee:"
+msgid "Employee"
+msgstr ""
+
+msgctxt "field:timesheet.line,cost_price:"
+msgid "Cost Price"
+msgstr ""
+
+msgctxt "help:company.employee,cost_price:"
+msgid "Hourly cost price for this Employee."
+msgstr ""
+
+msgctxt "help:company.employee,cost_prices:"
+msgid "List of hourly cost price over time."
+msgstr ""
+
+msgctxt "help:company.employee_cost_price,cost_price:"
+msgid "Hourly cost price."
+msgstr ""
+
+msgctxt "model:company.employee_cost_price,name:"
+msgid "Employee Cost Price"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_employee_unique_cost_price_date"
+msgid "An employee can only have one cost price by date."
+msgstr ""
+
+msgctxt "view:company.employee:"
+msgid "Cost"
+msgstr ""
diff --git a/modules/timesheet_cost/message.xml b/modules/timesheet_cost/message.xml
new file mode 100644
index 0000000000..ce99310cbe
--- /dev/null
+++ b/modules/timesheet_cost/message.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ An employee can only have one cost price by date.
+
+
+
diff --git a/modules/timesheet_cost/setup.py b/modules/timesheet_cost/setup.py
new file mode 100755
index 0000000000..47fff9b2b4
--- /dev/null
+++ b/modules/timesheet_cost/setup.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_timesheet_cost'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = ['python-sql >= 0.4']
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to add cost on timesheet',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/timesheet_cost',
+ },
+ keywords='tryton timesheet cost',
+ package_dir={'trytond.modules.timesheet_cost': '.'},
+ packages=(
+ ['trytond.modules.timesheet_cost']
+ + ['trytond.modules.timesheet_cost.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.timesheet_cost': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'Intended Audience :: Manufacturing',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ timesheet_cost = trytond.modules.timesheet_cost
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ )
diff --git a/modules/timesheet_cost/tests/__init__.py b/modules/timesheet_cost/tests/__init__.py
new file mode 100644
index 0000000000..a8b8a49a3d
--- /dev/null
+++ b/modules/timesheet_cost/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.timesheet_cost.tests.test_timesheet_cost import suite
+except ImportError:
+ from .test_timesheet_cost import suite
+
+__all__ = ['suite']
diff --git a/modules/timesheet_cost/tests/test_timesheet_cost.py b/modules/timesheet_cost/tests/test_timesheet_cost.py
new file mode 100644
index 0000000000..f515184db9
--- /dev/null
+++ b/modules/timesheet_cost/tests/test_timesheet_cost.py
@@ -0,0 +1,58 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import unittest
+import datetime
+from decimal import Decimal
+
+import trytond.tests.test_tryton
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.pool import Pool
+
+from trytond.modules.company.tests import (
+ create_company, set_company, create_employee, CompanyTestMixin)
+
+
+class TimesheetCostTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test TimesheetCost module'
+ module = 'timesheet_cost'
+
+ @with_transaction()
+ def test_compute_cost_price(self):
+ 'Test compute_cost_price'
+ pool = Pool()
+ Party = pool.get('party.party')
+ EmployeeCostPrice = pool.get('company.employee_cost_price')
+
+ cost_prices = [
+ (datetime.date(2011, 1, 1), Decimal(10)),
+ (datetime.date(2012, 1, 1), Decimal(15)),
+ (datetime.date(2013, 1, 1), Decimal(20)),
+ ]
+ test_prices = [
+ (datetime.date(2010, 1, 1), 0),
+ (datetime.date(2011, 1, 1), Decimal(10)),
+ (datetime.date(2011, 6, 1), Decimal(10)),
+ (datetime.date(2012, 1, 1), Decimal(15)),
+ (datetime.date(2012, 6, 1), Decimal(15)),
+ (datetime.date(2013, 1, 1), Decimal(20)),
+ (datetime.date(2013, 6, 1), Decimal(20)),
+ ]
+ party = Party(name='Pam Beesly')
+ party.save()
+ company = create_company()
+ with set_company(company):
+ employee = create_employee(company)
+ for date, cost_price in cost_prices:
+ EmployeeCostPrice(
+ employee=employee,
+ date=date,
+ cost_price=cost_price).save()
+ for date, cost_price in test_prices:
+ self.assertEqual(employee.compute_cost_price(date), cost_price)
+
+
+def suite():
+ suite = trytond.tests.test_tryton.suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ TimesheetCostTestCase))
+ return suite
diff --git a/modules/timesheet_cost/timesheet.py b/modules/timesheet_cost/timesheet.py
new file mode 100644
index 0000000000..85f1c2e799
--- /dev/null
+++ b/modules/timesheet_cost/timesheet.py
@@ -0,0 +1,77 @@
+# 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 sql import Null
+
+from trytond.transaction import Transaction
+from trytond.pool import Pool, PoolMeta
+from trytond.model import fields
+
+from .company import price_digits
+
+
+class Line(metaclass=PoolMeta):
+ __name__ = 'timesheet.line'
+
+ cost_price = fields.Numeric('Cost Price',
+ digits=price_digits, required=True, readonly=True)
+
+ @classmethod
+ def __register__(cls, module_name):
+ pool = Pool()
+ Employee = pool.get('company.employee')
+
+ transaction = Transaction()
+ cursor = transaction.connection.cursor()
+ update = transaction.connection.cursor()
+ table = cls.__table__()
+ table_h = cls.__table_handler__(module_name)
+
+ migrate_cost_price = not table_h.column_exist('cost_price')
+
+ super().__register__(module_name)
+
+ # Migration from 3.6: add cost_price
+ if migrate_cost_price:
+ cursor.execute(*table.select(table.id, table.employee, table.date,
+ where=(table.cost_price == 0)
+ & (table.employee != Null)
+ & (table.date != Null)))
+ for line_id, employee_id, date in cursor:
+ employee = Employee(employee_id)
+ cost_price = employee.compute_cost_price(date=date)
+ update.execute(*table.update(
+ [table.cost_price],
+ [cost_price],
+ where=table.id == line_id))
+
+ @classmethod
+ def default_cost_price(cls):
+ # Needed at creation as cost_price is required
+ return 0
+
+ @classmethod
+ def create(cls, vlist):
+ # XXX Remove cost_price because proteus set it as default value
+ vlist = [v.copy() for v in vlist]
+ for values in vlist:
+ values.pop('cost_price', None)
+ lines = super().create(vlist)
+ cls.sync_cost(lines)
+ return lines
+
+ @classmethod
+ def write(cls, *args):
+ super().write(*args)
+ cls.sync_cost(sum(args[0:None:2], []))
+
+ @classmethod
+ def sync_cost(cls, lines):
+ with Transaction().set_context(_check_access=False):
+ to_write = []
+ lines = cls.browse(lines)
+ for line in lines:
+ cost_price = line.employee.compute_cost_price(date=line.date)
+ if cost_price != line.cost_price:
+ to_write.extend([[line], {'cost_price': cost_price}])
+ if to_write:
+ cls.write(*to_write)
diff --git a/modules/timesheet_cost/timesheet.xml b/modules/timesheet_cost/timesheet.xml
new file mode 100644
index 0000000000..c8b33c898c
--- /dev/null
+++ b/modules/timesheet_cost/timesheet.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/timesheet_cost/tox.ini b/modules/timesheet_cost/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/timesheet_cost/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/timesheet_cost/tryton.cfg b/modules/timesheet_cost/tryton.cfg
new file mode 100644
index 0000000000..7fd8b72b4c
--- /dev/null
+++ b/modules/timesheet_cost/tryton.cfg
@@ -0,0 +1,11 @@
+[tryton]
+version=6.0.1
+depends:
+ ir
+ company
+ party
+ timesheet
+xml:
+ company.xml
+ timesheet.xml
+ message.xml
diff --git a/modules/timesheet_cost/view/employee_cost_price_form.xml b/modules/timesheet_cost/view/employee_cost_price_form.xml
new file mode 100644
index 0000000000..c1eaedc73b
--- /dev/null
+++ b/modules/timesheet_cost/view/employee_cost_price_form.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/modules/timesheet_cost/view/employee_cost_price_tree.xml b/modules/timesheet_cost/view/employee_cost_price_tree.xml
new file mode 100644
index 0000000000..0cd0e159a0
--- /dev/null
+++ b/modules/timesheet_cost/view/employee_cost_price_tree.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/modules/timesheet_cost/view/employee_form.xml b/modules/timesheet_cost/view/employee_form.xml
new file mode 100644
index 0000000000..ca069d26ff
--- /dev/null
+++ b/modules/timesheet_cost/view/employee_form.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/user_role/.drone.yml b/modules/user_role/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/user_role/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/user_role/.flake8 b/modules/user_role/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/user_role/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/user_role/CHANGELOG b/modules/user_role/CHANGELOG
new file mode 100644
index 0000000000..5bfe78f77d
--- /dev/null
+++ b/modules/user_role/CHANGELOG
@@ -0,0 +1,15 @@
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Initial release
diff --git a/modules/user_role/COPYRIGHT b/modules/user_role/COPYRIGHT
new file mode 100644
index 0000000000..1ed808dd77
--- /dev/null
+++ b/modules/user_role/COPYRIGHT
@@ -0,0 +1,14 @@
+Copyright (C) 2018-2021 Cédric Krier
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/user_role/LICENSE b/modules/user_role/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/user_role/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/user_role/MANIFEST.in b/modules/user_role/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/user_role/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/user_role/README.rst b/modules/user_role/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/user_role/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/user_role/__init__.py b/modules/user_role/__init__.py
new file mode 100644
index 0000000000..752dca49bf
--- /dev/null
+++ b/modules/user_role/__init__.py
@@ -0,0 +1,22 @@
+# 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 res
+from . import ir
+
+__all__ = ['register']
+
+
+def register():
+ Pool.register(
+ res.Role,
+ res.RoleGroup,
+ res.User,
+ res.UserRole,
+ ir.Cron,
+ module='user_role', type_='model')
+ Pool.register(
+ module='user_role', type_='wizard')
+ Pool.register(
+ module='user_role', type_='report')
diff --git a/modules/user_role/doc/index.rst b/modules/user_role/doc/index.rst
new file mode 100644
index 0000000000..17f9e219f1
--- /dev/null
+++ b/modules/user_role/doc/index.rst
@@ -0,0 +1,6 @@
+User Role Module
+################
+
+The user_role module allows to assign roles to user instead of groups.
+A *Role* is defined by a set of groups. When a role is added to a user, it
+overrides the existing groups. A role can be added to a user for a period.
diff --git a/modules/user_role/ir.py b/modules/user_role/ir.py
new file mode 100644
index 0000000000..e9897f816a
--- /dev/null
+++ b/modules/user_role/ir.py
@@ -0,0 +1,13 @@
+# 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 PoolMeta
+
+
+class Cron(metaclass=PoolMeta):
+ __name__ = 'ir.cron'
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.method.selection.append(
+ ('res.user|sync_roles', "Synchronize Users Roles"))
diff --git a/modules/user_role/locale/bg.po b/modules/user_role/locale/bg.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/bg.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/ca.po b/modules/user_role/locale/ca.po
new file mode 100644
index 0000000000..3575e9fa77
--- /dev/null
+++ b/modules/user_role/locale/ca.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Grups"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nom"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Grup"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Rols"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Des de la data"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Fins a la data"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Usuari"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Rols"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Rols"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Rol - Grup"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Rol d'usuari"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Sincronitza rols d'usuari"
diff --git a/modules/user_role/locale/cs.po b/modules/user_role/locale/cs.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/cs.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/de.po b/modules/user_role/locale/de.po
new file mode 100644
index 0000000000..86f4a8843a
--- /dev/null
+++ b/modules/user_role/locale/de.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Gruppen"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Name"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Gruppe"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Rolle"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Rollen"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Von Datum"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Rolle"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Bis Datum"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Benutzer"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Rollen"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Rollen"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Rolle"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Rolle - Gruppe"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Benutzerrolle"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Benutzerrollen synchronisieren"
diff --git a/modules/user_role/locale/es.po b/modules/user_role/locale/es.po
new file mode 100644
index 0000000000..786c4968aa
--- /dev/null
+++ b/modules/user_role/locale/es.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Grupos"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nombre"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Grupo"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Roles"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Desde la fecha"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Hasta la fecha"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Usuario"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Roles"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Roles"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Rol - Grupo"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Rol de usuario"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Sincronizar roles de usuario"
diff --git a/modules/user_role/locale/es_419.po b/modules/user_role/locale/es_419.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/es_419.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/et.po b/modules/user_role/locale/et.po
new file mode 100644
index 0000000000..cec0574672
--- /dev/null
+++ b/modules/user_role/locale/et.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Grupid"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nimi"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Grupp"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Roll"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Rollid"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Kuupäevast"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Roll"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Kuupäevani"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Kasutaja"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Rollid"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Rollid"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Roll"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Roll - Grupp"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Kasutajaroll"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Sünkroniseeri kasutajarollid"
diff --git a/modules/user_role/locale/fa.po b/modules/user_role/locale/fa.po
new file mode 100644
index 0000000000..a0e1bcd5ca
--- /dev/null
+++ b/modules/user_role/locale/fa.po
@@ -0,0 +1,64 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "گروه ها"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "نام"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "گروه"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "نقش، وظیفه"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "نقش ها، وظایف"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "از تاریخ"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "نقش، وظیفه"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "تا تاریخ"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "کاربر"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "نقش ها، وظایف"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "نقش ها، وظایف"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "نقش، وظیفه"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "گروه - نقش، وظیفه"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "نقش کاربر"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "هماهنگ سازی نقش های کاربران"
diff --git a/modules/user_role/locale/fi.po b/modules/user_role/locale/fi.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/fi.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/fr.po b/modules/user_role/locale/fr.po
new file mode 100644
index 0000000000..1ea67f1227
--- /dev/null
+++ b/modules/user_role/locale/fr.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Groupes"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nom"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Groupe"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Rôle"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Rôles"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Date de début"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Rôle"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Date de fin"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Utilisateur"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Rôles"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Rôles"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Rôle"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Rôle - Groupe"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Rôle utilisateur"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Synchroniser les utilisateurs et rôles"
diff --git a/modules/user_role/locale/hu.po b/modules/user_role/locale/hu.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/hu.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/id.po b/modules/user_role/locale/id.po
new file mode 100644
index 0000000000..3ee7dfa18a
--- /dev/null
+++ b/modules/user_role/locale/id.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nama"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Kelompok"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Dari Tanggal"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Ke Tanggal"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Pengguna"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/it.po b/modules/user_role/locale/it.po
new file mode 100644
index 0000000000..1ee2eabc48
--- /dev/null
+++ b/modules/user_role/locale/it.po
@@ -0,0 +1,64 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Gruppi"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nome"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Gruppo"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Ruolo"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Ruoli"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Dalla data"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Ruolo"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "A data"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Utente"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Ruoli"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Ruoli"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Ruolo"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Ruolo - Gruppo"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Ruolo utente"
+
+#, fuzzy
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Sincronizza ruoli utenti"
diff --git a/modules/user_role/locale/lo.po b/modules/user_role/locale/lo.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/lo.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/lt.po b/modules/user_role/locale/lt.po
new file mode 100644
index 0000000000..89260adfda
--- /dev/null
+++ b/modules/user_role/locale/lt.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Naudotojas"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Naudotojo vaidmenys"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/nl.po b/modules/user_role/locale/nl.po
new file mode 100644
index 0000000000..d7e7dd546f
--- /dev/null
+++ b/modules/user_role/locale/nl.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Groepen"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Naam"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Groep"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Rollen"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Vanaf datum"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Einddatum"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Gebruiker"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Rollen"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Rollen"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Rol"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Rol - groep"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Gebruikersrol"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Gebruikersrollen synchroniseren"
diff --git a/modules/user_role/locale/pl.po b/modules/user_role/locale/pl.po
new file mode 100644
index 0000000000..9237acb213
--- /dev/null
+++ b/modules/user_role/locale/pl.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Grupy"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "Nazwa"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "Grupa"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "Rola"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "Role"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "Od"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "Rola"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "Do"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "Użytkownik"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "Role"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "Role"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "Rola"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "Rola - Grupa"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "Rola użytkownika"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "Synchronizuj role użytkowników"
diff --git a/modules/user_role/locale/pt.po b/modules/user_role/locale/pt.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/pt.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/ro.po b/modules/user_role/locale/ro.po
new file mode 100644
index 0000000000..99ae204f58
--- /dev/null
+++ b/modules/user_role/locale/ro.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "Grupuri"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/ru.po b/modules/user_role/locale/ru.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/ru.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/sl.po b/modules/user_role/locale/sl.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/sl.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/tr.po b/modules/user_role/locale/tr.po
new file mode 100644
index 0000000000..b0151df4b4
--- /dev/null
+++ b/modules/user_role/locale/tr.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr ""
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr ""
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr ""
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr ""
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr ""
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr ""
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr ""
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr ""
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr ""
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr ""
diff --git a/modules/user_role/locale/zh_CN.po b/modules/user_role/locale/zh_CN.po
new file mode 100644
index 0000000000..9f4d01324c
--- /dev/null
+++ b/modules/user_role/locale/zh_CN.po
@@ -0,0 +1,63 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:res.role,groups:"
+msgid "Groups"
+msgstr "组"
+
+msgctxt "field:res.role,name:"
+msgid "Name"
+msgstr "名称"
+
+msgctxt "field:res.role-res.group,group:"
+msgid "Group"
+msgstr "组"
+
+msgctxt "field:res.role-res.group,role:"
+msgid "Role"
+msgstr "角色"
+
+msgctxt "field:res.user,roles:"
+msgid "Roles"
+msgstr "角色"
+
+msgctxt "field:res.user.role,from_date:"
+msgid "From Date"
+msgstr "从日期"
+
+msgctxt "field:res.user.role,role:"
+msgid "Role"
+msgstr "角色"
+
+msgctxt "field:res.user.role,to_date:"
+msgid "To Date"
+msgstr "到日期"
+
+msgctxt "field:res.user.role,user:"
+msgid "User"
+msgstr "用户"
+
+msgctxt "model:ir.action,name:act_role_form"
+msgid "Roles"
+msgstr "角色"
+
+msgctxt "model:ir.ui.menu,name:menu_role_form"
+msgid "Roles"
+msgstr "角色"
+
+msgctxt "model:res.role,name:"
+msgid "Role"
+msgstr "角色"
+
+msgctxt "model:res.role-res.group,name:"
+msgid "Role - Group"
+msgstr "角色-组"
+
+msgctxt "model:res.user.role,name:"
+msgid "User Role"
+msgstr "用户角色"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Synchronize Users Roles"
+msgstr "同步用户角色"
diff --git a/modules/user_role/res.py b/modules/user_role/res.py
new file mode 100644
index 0000000000..9b437ed6de
--- /dev/null
+++ b/modules/user_role/res.py
@@ -0,0 +1,121 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+
+from trytond.model import ModelSQL, ModelView, fields
+from trytond.pool import PoolMeta, Pool
+from trytond.pyson import Eval, If, Bool
+
+
+class Role(ModelSQL, ModelView):
+ "Role"
+ __name__ = 'res.role'
+ name = fields.Char("Name", required=True, translate=True)
+ groups = fields.Many2Many('res.role-res.group', 'role', 'group', "Groups")
+
+ @classmethod
+ def write(cls, *args):
+ pool = Pool()
+ User = pool.get('res.user')
+ roles = sum(args[0:None:2], [])
+ super().write(*args)
+
+ users = User.search([
+ ('roles.role', 'in', [r.id for r in roles]),
+ ])
+ User.sync_roles(users)
+
+
+class RoleGroup(ModelSQL):
+ "Role - Group"
+ __name__ = 'res.role-res.group'
+ role = fields.Many2One(
+ 'res.role', "Role", ondelete='CASCADE', select=True, required=True)
+ group = fields.Many2One(
+ 'res.group', "Group", ondelete='CASCADE', required=True)
+
+
+class User(metaclass=PoolMeta):
+ __name__ = 'res.user'
+ roles = fields.One2Many('res.user.role', 'user', "Roles")
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ group_readonly = Bool(Eval('roles', []))
+ if 'readonly' in cls.groups.states:
+ group_readonly |= cls.groups.states['readonly']
+ cls.groups.states['readonly'] = group_readonly
+
+ cls._context_fields.append('roles')
+
+ @classmethod
+ def create(cls, vlist):
+ users = super().create(vlist)
+ cls.sync_roles(users)
+ return users
+
+ @classmethod
+ def write(cls, *args):
+ users = sum(args[0:None:2], [])
+ super().write(*args)
+ cls.sync_roles(users)
+
+ @classmethod
+ def sync_roles(cls, users=None, date=None):
+ pool = Pool()
+ Date = pool.get('ir.date')
+ if date is None:
+ date = Date.today()
+ if users is None:
+ users = cls.search([])
+ to_write = []
+ for user in users:
+ if not user.roles:
+ continue
+ new = {g.id for r in user.roles for g in r.role.groups
+ if r.valid(date)}
+ old = {g.id for g in user.groups}
+ if new != old:
+ to_write.append([user])
+ to_write.append({'groups': [
+ ('add', new - old),
+ ('remove', old - new),
+ ]})
+ if to_write:
+ cls.write(*to_write)
+
+
+class UserRole(ModelSQL, ModelView):
+ "User Role"
+ __name__ = 'res.user.role'
+ user = fields.Many2One(
+ 'res.user', "User", ondelete='CASCADE', select=True, required=True)
+ role = fields.Many2One('res.role', "Role", required=True)
+ from_date = fields.Date(
+ "From Date",
+ domain=[
+ If(Eval('from_date') & Eval('to_date'),
+ ('from_date', '<=', Eval('to_date', None)),
+ ()),
+ ],
+ depends=['to_date'])
+ to_date = fields.Date(
+ "To Date",
+ domain=[
+ If(Eval('from_date') & Eval('to_date'),
+ ('to_date', '>=', Eval('from_date', None)),
+ ()),
+ ],
+ depends=['from_date'])
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls._order.insert(0, ('from_date', 'DESC NULLS FIRST'))
+ cls._order.insert(1, ('to_date', 'ASC NULLS FIRST'))
+
+ def valid(self, date):
+ from_date = self.from_date or datetime.date.min
+ to_date = self.to_date or datetime.date.max
+ return from_date <= date <= to_date
diff --git a/modules/user_role/res.xml b/modules/user_role/res.xml
new file mode 100644
index 0000000000..e8c6bf33b4
--- /dev/null
+++ b/modules/user_role/res.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+ res.role
+ form
+ role_form
+
+
+
+ res.role
+ tree
+ role_list
+
+
+
+ Roles
+ res.role
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ res.user
+
+ user_form
+
+
+
+ res.user.role
+ form
+ user_role_form
+
+
+
+ res.user.role
+ tree
+ user_role_list
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ res.user|sync_roles
+
+ days
+
+
+
diff --git a/modules/user_role/setup.py b/modules/user_role/setup.py
new file mode 100755
index 0000000000..a4f851685a
--- /dev/null
+++ b/modules/user_role/setup.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_user_role'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [get_require_version('proteus')]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to manage role on user',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/user_role',
+ },
+ keywords='user role group access',
+ package_dir={'trytond.modules.user_role': '.'},
+ packages=(
+ ['trytond.modules.user_role']
+ + ['trytond.modules.user_role.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.user_role': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.fodt',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ user_role = trytond.modules.user_role
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/user_role/tests/__init__.py b/modules/user_role/tests/__init__.py
new file mode 100644
index 0000000000..fea26d7b56
--- /dev/null
+++ b/modules/user_role/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.user_role.tests.test_user_role import suite
+except ImportError:
+ from .test_user_role import suite
+
+__all__ = ['suite']
diff --git a/modules/user_role/tests/scenario_user_role.rst b/modules/user_role/tests/scenario_user_role.rst
new file mode 100644
index 0000000000..63d98c2a3a
--- /dev/null
+++ b/modules/user_role/tests/scenario_user_role.rst
@@ -0,0 +1,88 @@
+==================
+User Role Scenario
+==================
+
+Imports::
+
+ >>> import datetime
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+
+Activate modules::
+
+ >>> config = activate_modules('user_role')
+
+Create some groups::
+
+ >>> Group = Model.get('res.group')
+ >>> groups = [Group(name="Group %s" % i) for i in range(5)]
+ >>> Group.save(groups)
+
+Create a role::
+
+ >>> Role = Model.get('res.role')
+ >>> role = Role(name="Role")
+ >>> role.groups.append(Group(groups[0].id))
+ >>> role.groups.append(Group(groups[1].id))
+ >>> role.save()
+
+Create a user with some groups::
+
+ >>> User = Model.get('res.user')
+ >>> user = User(login="user")
+ >>> user.groups.append(Group(groups[1].id))
+ >>> user.groups.append(Group(groups[2].id))
+ >>> user.groups.append(Group(groups[3].id))
+ >>> user.save()
+ >>> len(user.groups)
+ 3
+
+Set the role to the user::
+
+ >>> user_role = user.roles.new()
+ >>> user_role.role = role
+ >>> user.save()
+
+ >>> len(user.groups)
+ 2
+ >>> user.groups == [groups[0], groups[1]]
+ True
+
+Start the role in the future::
+
+ >>> user_role, = user.roles
+ >>> user_role.from_date = datetime.date.today() + datetime.timedelta(days=1)
+ >>> user_role.to_date = None
+ >>> user.save()
+
+ >>> len(user.groups)
+ 0
+
+End the role in the past::
+
+ >>> user_role, = user.roles
+ >>> user_role.from_date = None
+ >>> user_role.to_date = datetime.date.today() - datetime.timedelta(days=1)
+ >>> user.save()
+
+ >>> len(user.groups)
+ 0
+
+Start the role in the past and end in the future::
+
+ >>> user_role, = user.roles
+ >>> user_role.from_date = datetime.date.today() - datetime.timedelta(days=1)
+ >>> user_role.to_date = datetime.date.today() + datetime.timedelta(days=1)
+ >>> user.save()
+
+ >>> len(user.groups)
+ 2
+
+Changing groups to role::
+
+ >>> role.groups.append(Group(groups[4].id))
+ >>> role.save()
+
+ >>> user.reload()
+ >>> len(user.groups)
+ 3
diff --git a/modules/user_role/tests/test_user_role.py b/modules/user_role/tests/test_user_role.py
new file mode 100644
index 0000000000..f8820a3ed8
--- /dev/null
+++ b/modules/user_role/tests/test_user_role.py
@@ -0,0 +1,28 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import unittest
+
+import doctest
+
+from trytond.tests.test_tryton import ModuleTestCase
+from trytond.tests.test_tryton import suite as test_suite
+from trytond.tests.test_tryton import doctest_teardown
+from trytond.tests.test_tryton import doctest_checker
+
+
+class UserRoleTestCase(ModuleTestCase):
+ 'Test User Role module'
+ module = 'user_role'
+
+
+def suite():
+ suite = test_suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ UserRoleTestCase))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_user_role.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ return suite
diff --git a/modules/user_role/tox.ini b/modules/user_role/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/user_role/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/user_role/tryton.cfg b/modules/user_role/tryton.cfg
new file mode 100644
index 0000000000..01ec4858d4
--- /dev/null
+++ b/modules/user_role/tryton.cfg
@@ -0,0 +1,7 @@
+[tryton]
+version=6.0.1
+depends:
+ ir
+ res
+xml:
+ res.xml
diff --git a/modules/user_role/view/role_form.xml b/modules/user_role/view/role_form.xml
new file mode 100644
index 0000000000..29e71a9b75
--- /dev/null
+++ b/modules/user_role/view/role_form.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/modules/user_role/view/role_list.xml b/modules/user_role/view/role_list.xml
new file mode 100644
index 0000000000..b4d89538b1
--- /dev/null
+++ b/modules/user_role/view/role_list.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/modules/user_role/view/user_form.xml b/modules/user_role/view/user_form.xml
new file mode 100644
index 0000000000..df84ab12ee
--- /dev/null
+++ b/modules/user_role/view/user_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/user_role/view/user_role_form.xml b/modules/user_role/view/user_role_form.xml
new file mode 100644
index 0000000000..dcc6f1a273
--- /dev/null
+++ b/modules/user_role/view/user_role_form.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/modules/user_role/view/user_role_list.xml b/modules/user_role/view/user_role_list.xml
new file mode 100644
index 0000000000..ead0d3eceb
--- /dev/null
+++ b/modules/user_role/view/user_role_list.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop/.drone.yml b/modules/web_shop/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/web_shop/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/web_shop/.flake8 b/modules/web_shop/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/web_shop/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/web_shop/CHANGELOG b/modules/web_shop/CHANGELOG
new file mode 100644
index 0000000000..004197f49f
--- /dev/null
+++ b/modules/web_shop/CHANGELOG
@@ -0,0 +1,14 @@
+Version 6.0.3 - 2022-08-03
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.2 - 2021-09-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.1 - 2021-06-17
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Initial release
\ No newline at end of file
diff --git a/modules/web_shop/COPYRIGHT b/modules/web_shop/COPYRIGHT
new file mode 100644
index 0000000000..77b68b5df9
--- /dev/null
+++ b/modules/web_shop/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2020-2022 Cédric Krier
+Copyright (C) 2020-2022 B2CK
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/web_shop/LICENSE b/modules/web_shop/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/web_shop/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/web_shop/MANIFEST.in b/modules/web_shop/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/web_shop/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/web_shop/README.rst b/modules/web_shop/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/web_shop/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/web_shop/__init__.py b/modules/web_shop/__init__.py
new file mode 100644
index 0000000000..4b1f3fceb1
--- /dev/null
+++ b/modules/web_shop/__init__.py
@@ -0,0 +1,28 @@
+# 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
+
+__all__ = ['register']
+
+from . import web
+from . import product
+from . import sale
+
+
+def register():
+ Pool.register(
+ web.Shop,
+ web.Shop_Warehouse,
+ web.Shop_Product,
+ web.Shop_ProductCategory,
+ web.User,
+ product.Product,
+ product.Category,
+ sale.Sale,
+ module='web_shop', type_='model')
+ Pool.register(
+ web.ShopAttribute,
+ web.Shop_Attribute,
+ product.Attribute,
+ module='web_shop', type_='model', depends=['product_attribute'])
diff --git a/modules/web_shop/doc/index.rst b/modules/web_shop/doc/index.rst
new file mode 100644
index 0000000000..ca1a1a8572
--- /dev/null
+++ b/modules/web_shop/doc/index.rst
@@ -0,0 +1,5 @@
+Web Shop Module
+###############
+
+The web_shop module provides facilities to store configuration of online web
+shop.
diff --git a/modules/web_shop/locale/bg.po b/modules/web_shop/locale/bg.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/bg.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/ca.po b/modules/web_shop/locale/ca.po
new file mode 100644
index 0000000000..2f462d7b7a
--- /dev/null
+++ b/modules/web_shop/locale/ca.po
@@ -0,0 +1,207 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr "Tendes web"
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr "Tendes web"
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr "Tendes web"
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr "ID Web"
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr "Tenda Web"
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr "Atributs"
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr "Atributes eliminats"
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr "Categories"
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr "Categories eliminades"
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr "Moneda"
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr "Tercer Invitat"
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr "Idioma"
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr "Nom"
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr "Productes"
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr "Productes eliminats"
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr "Tipus"
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr "Magatzems"
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr "Atribut"
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr "Tenda"
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr "Categoria"
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr "Tenda"
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr "Producte"
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr "Tenda"
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr "Tenda"
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr "Magatzem"
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr "Adreça de facturació"
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr "Adreça d'enviament"
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr "La llista de tendes online on s'ha publicat l'atribut."
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr "La llista de tendes on esta publicada la categoria."
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr "La llista de tendes on esta publicat el producte."
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr "La llista d'atributs a publicar."
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr "La llista d'atributs a despublicar."
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr "La llsita de categories a publicar."
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr "La llsita de categories a despublicar."
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr "La llsita de productes a publicar."
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr "La llsita de productes a despublicar."
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr "El front-end utiltizat per aquesta tenda."
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr "Tendes web"
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+"Per a processar la venda \"%(sale)s\" heu de seleccionar un tercer diferent "
+"del invitat \"%(party)s\"."
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr "El ID de la tenda web ha de ser únic."
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr "El nom de la tenda ha de ser únic."
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr "Usuari a les empreses"
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr "Tendes web"
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr "Tenda Web"
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr "Tenda Web - Atribut"
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr "Tenda web - Categoria de producte"
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr "Tenda web - Producte"
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr "Tenda web - Magatzem"
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr "Productes"
diff --git a/modules/web_shop/locale/cs.po b/modules/web_shop/locale/cs.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/cs.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/de.po b/modules/web_shop/locale/de.po
new file mode 100644
index 0000000000..200203446a
--- /dev/null
+++ b/modules/web_shop/locale/de.po
@@ -0,0 +1,207 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr "Webshops"
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr "Webshops"
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr "Webshops"
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr "Web ID"
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr "Webshop"
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr "Attribute"
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr "Entfernte Attribute"
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr "Kategorien"
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr "Entfernte Kategorien"
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr "Unternehmen"
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr "Währung"
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr "Partei für Gäste"
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr "Sprache"
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr "Name"
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr "Artikel"
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr "Entfernte Artikel"
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr "Typ"
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr "Warenlager"
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr "Attribut"
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr "Webshop"
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr "Kategorie"
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr "Webshop"
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr "Artikel"
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr "Webshop"
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr "Webshop"
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr "Warenlager"
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr "Rechnungsadresse"
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr "Lieferadresse"
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr "Die Liste der Webshops auf denen das Attribut veröffentlicht wird."
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr "Die Liste von Webshop auf dem die Kategorie veröffentlicht wird."
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr "Die Liste der Webshops in denen der Artikel veröffentlicht wird."
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr "Liste der zu veröffentlichenden Attribute."
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr "Liste der von der Veröffentlichung zurückzuziehenden Artikel."
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr "Liste der zu veröffentlichenden Kategorien."
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr "Liste der von der Veröffentlichung zurückzuziehenden Kategorien."
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr "Liste der zu veröffentlichenden Artikel."
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr "Liste der von der Veröffentlichung zurückzuziehenden Artikel."
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr "Das für den Webshop genutzte Frontend."
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr "Webshops"
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+"Um Verkauf \"%(sale)s\" auszuführen muss eine andere Partei als die "
+"Gastpartei \"%(party)s\" ausgewählt werden."
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr "Die Web ID auf dem Verkauf muss eindeutig sein."
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr "Der Name des Webshops muss eindeutig sein."
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr "Benutzer im Unternehmen"
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr "Webshops"
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr "Webshop"
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr "Webshop - Attribut"
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr "Webshop - Artikelkategorie"
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr "Webshop - Artikel"
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr "Webshop - Warenlager"
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr "Artikel"
diff --git a/modules/web_shop/locale/es.po b/modules/web_shop/locale/es.po
new file mode 100644
index 0000000000..ee49e33b6f
--- /dev/null
+++ b/modules/web_shop/locale/es.po
@@ -0,0 +1,207 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr "Tiendas Web"
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr "Tiendas Web"
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr "Tiendas Web"
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr "ID Web"
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr "Tienda Web"
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr "Atributos"
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr "Atributos eliminados"
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr "Categorías"
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr "Categorías eliminadas"
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr "Empresa"
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr "Moneda"
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr "Tercero invitado"
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr "Idioma"
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr "Nombre"
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr "Productos"
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr "Productos Eliminados"
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr "Tipo"
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr "Almacenes"
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr "Atributo"
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr "Tienda"
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr "Categoría"
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr "Tienda"
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr "Producto"
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr "Tienda"
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr "Tienda"
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr "Almacén"
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr "Dirección de facturación"
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr "Dirección de envío"
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr "La lista de tiendas web donde se publica el atributo."
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr "La lista de tiendas en que la categoría esta pública."
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr "La lista de tiendas en que el producto se ha publicado."
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr "La lista de atributos a publicar."
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr "La lista de atributos a despublicar."
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr "La lista de categorias a publicar."
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr "La lista de categorias a despublicar."
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr "La lista de produtos a publicar."
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr "La lista de categorias a despublicar."
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr "El frontend utilizado para la tienda web."
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr "Tiendas Web"
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+"Para procesar la venta \"%(sale)s\" debéis seleccionar un cliente distinto "
+"al invitado\"%(party)s\"."
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr "El id web de la venta debe ser único."
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr "El nombre de la tienda debe ser único."
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr "Usuario en las empresas"
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr "Tiendas Web"
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr "Tienda Web"
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr "Tienda Web - Atributo"
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr "Tienda Web - Categoría Producto"
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr "Tienda Web - Producto"
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr "Tienda Web - Almacén"
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr "Productos"
diff --git a/modules/web_shop/locale/es_419.po b/modules/web_shop/locale/es_419.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/es_419.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/et.po b/modules/web_shop/locale/et.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/et.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/fa.po b/modules/web_shop/locale/fa.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/fa.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/fi.po b/modules/web_shop/locale/fi.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/fi.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/fr.po b/modules/web_shop/locale/fr.po
new file mode 100644
index 0000000000..5236a1b5ca
--- /dev/null
+++ b/modules/web_shop/locale/fr.po
@@ -0,0 +1,208 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr "Boutiques en ligne"
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr "Boutiques en ligne"
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr "Boutiques en ligne"
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr "ID Web"
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr "Boutique en ligne"
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr "Attributs"
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr "Attributs supprimés"
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr "Catégories"
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr "Catégories supprimées"
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr "Société"
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr "Devise"
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr "Tiers invité"
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr "Langue"
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr "Nom"
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr "Produits"
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr "Produits supprimés"
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr "Type"
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr "Entrepôts"
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr "Attribut"
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr "Boutique"
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr "Catégorie"
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr "Boutique"
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr "Produit"
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr "Boutique"
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr "Boutique"
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr "Entrepôt"
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr "Adresse de facturation"
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr "Adresse d'expédition"
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr "La liste des boutiques en ligne sur lesquelles l'attribut est publié."
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+"La liste des boutiques en ligne sur lesquelles la catégorie est publiée."
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr "La liste des boutiques en ligne sur lesquelles le produit est publié."
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr "La liste des attributs à publier."
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr "La liste des attributs à dépublier."
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr "La liste des catégories à publier."
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr "La liste des catégories à dépublier."
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr "La liste des produits à publier."
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr "La liste des produits à dépublier."
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr "Le frontal utilisé pour la boutique en ligne."
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr "Boutiques en ligne"
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+"Pour traiter la vente « %(sale)s », vous devez sélectionner un autre tiers à"
+" la place de l'invité « %(party)s »."
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr "L'ID web de la vente doit être unique."
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr "Le nom de la boutique en ligne doit être unique."
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr "Utilisateur dans les sociétés"
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr "Boutiques en ligne"
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr "Boutique en ligne"
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr "Boutique Web - Attribut"
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr "Boutique en ligne - Catégorie de produit"
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr "Boutique en ligne - Produit"
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr "Boutique en ligne - Entrepôt"
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr "Produits"
diff --git a/modules/web_shop/locale/hu.po b/modules/web_shop/locale/hu.po
new file mode 100644
index 0000000000..ba23afe76f
--- /dev/null
+++ b/modules/web_shop/locale/hu.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr "Webáruházak"
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr "Webáruházak"
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr "Webáruházak"
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr "Webáruház"
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr "Jellemző értékek"
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr "Kategóriák"
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr "Cég"
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr "Pénznem"
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr "Vendég ügyfél"
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr "Nyelv"
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr "Név"
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr "Termékek"
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr "Típus"
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr "Raktárak"
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr "Jellemző érték"
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr "Bolt"
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr "Kategória"
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr "Bolt"
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr "Termék"
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr "Bolt"
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr "Bolt"
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr "Raktár"
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr "Számlázási cím"
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr "Szállítási cím"
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr "Webáruházak"
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr "Webáruházak"
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr "Webáruház"
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr "Termékek"
diff --git a/modules/web_shop/locale/id.po b/modules/web_shop/locale/id.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/id.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/it.po b/modules/web_shop/locale/it.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/it.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/lo.po b/modules/web_shop/locale/lo.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/lo.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/lt.po b/modules/web_shop/locale/lt.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/lt.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/nl.po b/modules/web_shop/locale/nl.po
new file mode 100644
index 0000000000..6a1570efa9
--- /dev/null
+++ b/modules/web_shop/locale/nl.po
@@ -0,0 +1,209 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr "Webwinkels"
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr "Webwinkels"
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr "Webwinkels"
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr "Web-ID"
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr "Webwinkels"
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr "attributen"
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr "verijderde attributen"
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr "Categorieën"
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr "verwijderde Categorieën"
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr "Bedrijf"
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr "Valuta"
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr "Gast Relatie"
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr "Taal"
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr "Naam"
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr "Producten"
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr "Verwijderde Producten"
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr "Type"
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr "Magazijn"
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr "Attribuut"
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr "Winkel"
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr "Categorie"
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr "Winkel"
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr "Product"
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr "Winkel"
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr "Winkel"
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr "Magazijn"
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr "Factuuradres"
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr "Afleveradres"
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr "De lijst met webwinkels waarop het attribuut is gepubliceerd."
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr "De lijst met webwinkels waarop de categorie is gepubliceerd."
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr "De lijst met webwinkels waarop het product is gepubliceerd."
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr "De lijst met te publiceren kenmerken."
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr "De lijst met te publiceren kenmerken."
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr "De lijst met te publiceren categorieën."
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+"De lijst met categorieën waarvan de publicatie ongedaan moet worden gemaakt."
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr "De lijst met te publiceren producten."
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+"De lijst met producten waarvan de publicatie ongedaan moet worden gemaakt."
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr "De front-end die wordt gebruikt voor de webshop."
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr "Webwinkels"
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+"Om de verkoop \"%(sale)s\" te verwerken, moet u een andere relatie "
+"selecteren in plaats van de gast \"%(party)s\"."
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr "De web-ID van de verkoop moet uniek zijn."
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr "De naam van de webshop moet uniek zijn."
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr "Webwinkels"
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr "Webwinkels"
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr "Webwinkel - Kenmerk"
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr "Webwinkel - Productcategorie"
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr "Webwinkel - Product"
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr "Webwinkel - Magazijn"
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr "Producten"
diff --git a/modules/web_shop/locale/pl.po b/modules/web_shop/locale/pl.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/pl.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/pt.po b/modules/web_shop/locale/pt.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/pt.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/ro.po b/modules/web_shop/locale/ro.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/ro.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/ru.po b/modules/web_shop/locale/ru.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/ru.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/sl.po b/modules/web_shop/locale/sl.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/sl.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/tr.po b/modules/web_shop/locale/tr.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/tr.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/locale/zh_CN.po b/modules/web_shop/locale/zh_CN.po
new file mode 100644
index 0000000000..e20a98e2a3
--- /dev/null
+++ b/modules/web_shop/locale/zh_CN.po
@@ -0,0 +1,205 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:product.attribute,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.category,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:product.product,web_shops:"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "field:sale.sale,web_id:"
+msgid "Web ID"
+msgstr ""
+
+msgctxt "field:sale.sale,web_shop:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "field:web.shop,attributes:"
+msgid "Attributes"
+msgstr ""
+
+msgctxt "field:web.shop,attributes_removed:"
+msgid "Attributes Removed"
+msgstr ""
+
+msgctxt "field:web.shop,categories:"
+msgid "Categories"
+msgstr ""
+
+msgctxt "field:web.shop,categories_removed:"
+msgid "Categories Removed"
+msgstr ""
+
+msgctxt "field:web.shop,company:"
+msgid "Company"
+msgstr ""
+
+msgctxt "field:web.shop,currency:"
+msgid "Currency"
+msgstr ""
+
+msgctxt "field:web.shop,guest_party:"
+msgid "Guest Party"
+msgstr ""
+
+msgctxt "field:web.shop,language:"
+msgid "Language"
+msgstr ""
+
+msgctxt "field:web.shop,name:"
+msgid "Name"
+msgstr ""
+
+msgctxt "field:web.shop,products:"
+msgid "Products"
+msgstr ""
+
+msgctxt "field:web.shop,products_removed:"
+msgid "Products Removed"
+msgstr ""
+
+msgctxt "field:web.shop,type:"
+msgid "Type"
+msgstr ""
+
+msgctxt "field:web.shop,warehouses:"
+msgid "Warehouses"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,attribute:"
+msgid "Attribute"
+msgstr ""
+
+msgctxt "field:web.shop-product.attribute,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,category:"
+msgid "Category"
+msgstr ""
+
+msgctxt "field:web.shop-product.category,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,product:"
+msgid "Product"
+msgstr ""
+
+msgctxt "field:web.shop-product.product,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,shop:"
+msgid "Shop"
+msgstr ""
+
+msgctxt "field:web.shop-stock.location,warehouse:"
+msgid "Warehouse"
+msgstr ""
+
+msgctxt "field:web.user,invoice_address:"
+msgid "Invoice Address"
+msgstr ""
+
+msgctxt "field:web.user,shipment_address:"
+msgid "Shipment Address"
+msgstr ""
+
+msgctxt "help:product.attribute,web_shops:"
+msgid "The list of web shops on which the attribute is published."
+msgstr ""
+
+msgctxt "help:product.category,web_shops:"
+msgid "The list of web shops on which the category is published."
+msgstr ""
+
+msgctxt "help:product.product,web_shops:"
+msgid "The list of web shops on which the product is published."
+msgstr ""
+
+msgctxt "help:web.shop,attributes:"
+msgid "The list of attributes to publish."
+msgstr ""
+
+msgctxt "help:web.shop,attributes_removed:"
+msgid "The list of attributes to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,categories:"
+msgid "The list of categories to publish."
+msgstr ""
+
+msgctxt "help:web.shop,categories_removed:"
+msgid "The list of categories to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,products:"
+msgid "The list of products to publish."
+msgstr ""
+
+msgctxt "help:web.shop,products_removed:"
+msgid "The list of products to unpublish."
+msgstr ""
+
+msgctxt "help:web.shop,type:"
+msgid "The front-end used for the web shop."
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_invalid_party"
+msgid ""
+"To process the sale \"%(sale)s\" you must select another party instead of "
+"the guest \"%(party)s\"."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_sale_web_id_unique"
+msgid "The web ID on the sale must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shop_name_unique"
+msgid "The web shop name must be unique."
+msgstr ""
+
+msgctxt "model:ir.rule.group,name:rule_group_shop_companies"
+msgid "User in companies"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shop"
+msgid "Web Shops"
+msgstr ""
+
+msgctxt "model:web.shop,name:"
+msgid "Web Shop"
+msgstr ""
+
+msgctxt "model:web.shop-product.attribute,name:"
+msgid "Web Shop - Attribute"
+msgstr ""
+
+msgctxt "model:web.shop-product.category,name:"
+msgid "Web Shop - Product Category"
+msgstr ""
+
+msgctxt "model:web.shop-product.product,name:"
+msgid "Web Shop - Product"
+msgstr ""
+
+msgctxt "model:web.shop-stock.location,name:"
+msgid "Web Shop - Warehouse"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Products"
+msgstr ""
diff --git a/modules/web_shop/message.xml b/modules/web_shop/message.xml
new file mode 100644
index 0000000000..8426d012ec
--- /dev/null
+++ b/modules/web_shop/message.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ The web shop name must be unique.
+
+
+ To process the sale "%(sale)s" you must select another party instead of the guest "%(party)s".
+
+
+ The web ID on the sale must be unique.
+
+
+
diff --git a/modules/web_shop/product.py b/modules/web_shop/product.py
new file mode 100644
index 0000000000..433149f913
--- /dev/null
+++ b/modules/web_shop/product.py
@@ -0,0 +1,34 @@
+# 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.model import fields
+from trytond.pool import PoolMeta
+from trytond.pyson import Eval
+
+
+class Product(metaclass=PoolMeta):
+ __name__ = 'product.product'
+
+ web_shops = fields.Many2Many(
+ 'web.shop-product.product', 'product', 'shop', "Web Shops",
+ states={
+ 'invisible': ~Eval('salable'),
+ },
+ depends=['salable'],
+ help="The list of web shops on which the product is published.")
+
+
+class Category(metaclass=PoolMeta):
+ __name__ = 'product.category'
+
+ web_shops = fields.Many2Many(
+ 'web.shop-product.category', 'category', 'shop', "Web Shops",
+ help="The list of web shops on which the category is published.")
+
+
+class Attribute(metaclass=PoolMeta):
+ __name__ = 'product.attribute'
+
+ web_shops = fields.Many2Many(
+ 'web.shop-product.attribute', 'attribute', 'shop', "Web Shops",
+ help="The list of web shops on which the attribute is published.")
diff --git a/modules/web_shop/product.xml b/modules/web_shop/product.xml
new file mode 100644
index 0000000000..fe374db258
--- /dev/null
+++ b/modules/web_shop/product.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ product.product
+
+ product_product_form
+
+
+
+ product.category
+
+ product_category_form
+
+
+
+
+
+ product.attribute
+
+ product_attribute_form
+
+
+
diff --git a/modules/web_shop/sale.py b/modules/web_shop/sale.py
new file mode 100644
index 0000000000..6bd24053e1
--- /dev/null
+++ b/modules/web_shop/sale.py
@@ -0,0 +1,74 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import binascii
+import os
+try:
+ from secrets import token_hex
+except ImportError:
+ def token_hex(nbytes=None):
+ if nbytes is None:
+ nbytes = 32
+ return binascii.hexlify(os.urandom(nbytes)).decode('ascii')
+
+from sql.conditionals import NullIf
+from sql.operators import Equal
+
+from trytond.i18n import gettext
+from trytond.model import Exclude, fields
+from trytond.pool import PoolMeta, Pool
+from trytond.pyson import Bool, Eval
+
+from trytond.modules.sale.exceptions import SaleValidationError
+
+
+class Sale(metaclass=PoolMeta):
+ __name__ = 'sale.sale'
+
+ web_shop = fields.Many2One('web.shop', "Web Shop", ondelete='RESTRICT')
+ web_id = fields.Char(
+ "Web ID",
+ states={
+ 'required': Bool(Eval('web_shop')),
+ 'readonly': ~Eval('web_id'),
+ },
+ depends=['web_shop'])
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ t = cls.__table__()
+ cls._sql_constraints += [
+ ('web_id', Exclude(t, (NullIf(t.web_id, ''), Equal)),
+ 'web_shop.msg_sale_web_id_unique')
+ ]
+
+ @fields.depends('web_shop', 'web_id')
+ def on_change_web_shop(self, nbytes=None):
+ if self.web_shop and not self.web_id:
+ self.web_id = token_hex(nbytes)
+
+ @classmethod
+ def copy(cls, sales, default=None):
+ if default is None:
+ default = {}
+ else:
+ default = default.copy()
+ default.setdefault('web_shop', None)
+ default.setdefault('web_id', None)
+ return super().copy(sales, default=default)
+
+ @classmethod
+ def validate(cls, sales):
+ pool = Pool()
+ WebShop = pool.get('web.shop')
+ super().validate(sales)
+
+ web_shops = WebShop.search([])
+ guests = {s.guest_party for s in web_shops}
+ for sale in sales:
+ if (sale.state not in {'draft', 'cancelled'}
+ and sale.party in guests):
+ raise SaleValidationError(
+ gettext('web_shop.msg_sale_invalid_party',
+ sale=sale.rec_name,
+ party=sale.party.rec_name))
diff --git a/modules/web_shop/sale.xml b/modules/web_shop/sale.xml
new file mode 100644
index 0000000000..f06ff244bb
--- /dev/null
+++ b/modules/web_shop/sale.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ sale.sale
+
+ sale_form
+
+
+
diff --git a/modules/web_shop/setup.py b/modules/web_shop/setup.py
new file mode 100755
index 0000000000..85b9159f1e
--- /dev/null
+++ b/modules/web_shop/setup.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname, slice=None):
+ content = io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+ if slice:
+ content = '\n'.join(content.splitlines()[slice])
+ return content
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_web_shop'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [get_require_version('trytond_product_attribute')]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module that provides a common base for webshops',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/web_shop',
+ },
+ keywords='webshop ecommerce',
+ package_dir={'trytond.modules.web_shop': '.'},
+ packages=(
+ ['trytond.modules.web_shop']
+ + ['trytond.modules.web_shop.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.web_shop': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.fodt',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ web_shop = trytond.modules.web_shop
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/web_shop/tests/__init__.py b/modules/web_shop/tests/__init__.py
new file mode 100644
index 0000000000..6cfc6f1aa0
--- /dev/null
+++ b/modules/web_shop/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.web_shop.tests.test_web_shop import suite
+except ImportError:
+ from .test_web_shop import suite
+
+__all__ = ['suite']
diff --git a/modules/web_shop/tests/test_web_shop.py b/modules/web_shop/tests/test_web_shop.py
new file mode 100644
index 0000000000..27c0933fc1
--- /dev/null
+++ b/modules/web_shop/tests/test_web_shop.py
@@ -0,0 +1,23 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import unittest
+
+
+from trytond.tests.test_tryton import ModuleTestCase
+from trytond.tests.test_tryton import suite as test_suite
+
+from trytond.modules.company.tests import CompanyTestMixin
+
+
+class WebShopTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test Web Shop module'
+ module = 'web_shop'
+ extras = ['product_attribute']
+
+
+def suite():
+ suite = test_suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ WebShopTestCase))
+ return suite
diff --git a/modules/web_shop/tox.ini b/modules/web_shop/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/web_shop/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/web_shop/tryton.cfg b/modules/web_shop/tryton.cfg
new file mode 100644
index 0000000000..8d88654ad4
--- /dev/null
+++ b/modules/web_shop/tryton.cfg
@@ -0,0 +1,18 @@
+[tryton]
+version=6.0.4
+depends:
+ account
+ company
+ currency
+ ir
+ product
+ sale
+ stock
+ web_user
+extras_depend:
+ product_attribute
+xml:
+ web.xml
+ product.xml
+ sale.xml
+ message.xml
diff --git a/modules/web_shop/view/product_attribute_form.xml b/modules/web_shop/view/product_attribute_form.xml
new file mode 100644
index 0000000000..958e7a5351
--- /dev/null
+++ b/modules/web_shop/view/product_attribute_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/web_shop/view/product_category_form.xml b/modules/web_shop/view/product_category_form.xml
new file mode 100644
index 0000000000..35755ffbb2
--- /dev/null
+++ b/modules/web_shop/view/product_category_form.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop/view/product_product_form.xml b/modules/web_shop/view/product_product_form.xml
new file mode 100644
index 0000000000..fbcb2cf2fd
--- /dev/null
+++ b/modules/web_shop/view/product_product_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/web_shop/view/sale_form.xml b/modules/web_shop/view/sale_form.xml
new file mode 100644
index 0000000000..3c3e64c54b
--- /dev/null
+++ b/modules/web_shop/view/sale_form.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop/view/shop_attribute_form.xml b/modules/web_shop/view/shop_attribute_form.xml
new file mode 100644
index 0000000000..8bfd78e0b0
--- /dev/null
+++ b/modules/web_shop/view/shop_attribute_form.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/modules/web_shop/view/shop_form.xml b/modules/web_shop/view/shop_form.xml
new file mode 100644
index 0000000000..f157187138
--- /dev/null
+++ b/modules/web_shop/view/shop_form.xml
@@ -0,0 +1,32 @@
+
+
+
diff --git a/modules/web_shop/view/shop_list.xml b/modules/web_shop/view/shop_list.xml
new file mode 100644
index 0000000000..3755d2d5ab
--- /dev/null
+++ b/modules/web_shop/view/shop_list.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop/view/user_form.xml b/modules/web_shop/view/user_form.xml
new file mode 100644
index 0000000000..fe668dde95
--- /dev/null
+++ b/modules/web_shop/view/user_form.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop/web.py b/modules/web_shop/web.py
new file mode 100644
index 0000000000..8d99f7da90
--- /dev/null
+++ b/modules/web_shop/web.py
@@ -0,0 +1,329 @@
+# 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 collections import defaultdict
+
+from sql import Literal
+from sql.operators import Equal
+
+from trytond.cache import Cache
+from trytond.model import (
+ ModelSQL, ModelView, DeactivableMixin, Exclude, fields)
+from trytond.transaction import Transaction
+from trytond.pool import Pool, PoolMeta
+from trytond.pyson import Eval
+
+
+class Many2ManyInactive(fields.Many2Many):
+
+ def get(self, ids, model, name, values=None):
+ with Transaction().set_context(inactive_test=True):
+ return super().get(ids, model, name, values=values)
+
+ def set(self, Model, name, ids, values, *args):
+ with Transaction().set_context(inactive_test=True):
+ return super().set(Model, name, ids, values, *args)
+
+
+class Inactivate(ModelSQL, DeactivableMixin):
+
+ @classmethod
+ def search_domain(cls, domain, active_test=True, tables=None):
+ context = Transaction().context
+ if context.get('inactive_test'):
+ domain = [domain, ('active', '=', False)]
+ return super().search_domain(
+ domain, active_test=active_test, tables=tables)
+
+ @classmethod
+ def delete(cls, records):
+ cls.copy([r for r in records if r.active and r.shop.to_sync],
+ default={
+ 'active': False,
+ })
+ super().delete(records)
+
+
+class Shop(DeactivableMixin, ModelSQL, ModelView):
+ "Web Shop"
+ __name__ = 'web.shop'
+
+ name = fields.Char("Name", required=True)
+ company = fields.Many2One('company.company', "Company", required=True)
+ currency = fields.Many2One('currency.currency', "Currency", required=True)
+ language = fields.Many2One(
+ 'ir.lang', "Language",
+ domain=[
+ ('translatable', '=', True),
+ ])
+ type = fields.Selection([
+ (None, ""),
+ ], "Type",
+ help="The front-end used for the web shop.")
+ warehouses = fields.Many2Many(
+ 'web.shop-stock.location', 'shop', 'warehouse', "Warehouses",
+ domain=[
+ ('type', '=', 'warehouse'),
+ ])
+ guest_party = fields.Many2One(
+ 'party.party', "Guest Party",
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'])
+
+ products = fields.Many2Many(
+ 'web.shop-product.product', 'shop', 'product', "Products",
+ domain=[
+ ('salable', '=', True),
+ ],
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'],
+ help="The list of products to publish.")
+ products_removed = Many2ManyInactive(
+ 'web.shop-product.product', 'shop', 'product', "Products Removed",
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'],
+ help="The list of products to unpublish.")
+
+ categories = fields.Many2Many(
+ 'web.shop-product.category', 'shop', 'category', "Categories",
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'],
+ help="The list of categories to publish.")
+ categories_removed = Many2ManyInactive(
+ 'web.shop-product.category', 'shop', 'category', "Categories Removed",
+ context={
+ 'company': Eval('company', -1),
+ },
+ depends=['company'],
+ help="The list of categories to unpublish.")
+
+ _name_cache = Cache('web.shop.name', context=False)
+
+ @property
+ def warehouse(self):
+ if self.warehouses:
+ return self.warehouses[0]
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+
+ t = cls.__table__()
+ cls._sql_constraints = [
+ ('name_unique', Exclude(t, (t.name, Equal),
+ where=t.active == Literal(True)),
+ 'web_shop.msg_shop_name_unique'),
+ ]
+
+ @classmethod
+ def default_company(cls):
+ return Transaction().context.get('company')
+
+ @classmethod
+ def default_currency(cls):
+ pool = Pool()
+ Company = pool.get('company.company')
+ company_id = cls.default_company()
+ if company_id:
+ company = Company(company_id)
+ return company.currency.id
+
+ @classmethod
+ def get(cls, name):
+ shop_id = cls._name_cache.get(name)
+ if not shop_id:
+ shop, = cls.search([('name', '=', name)])
+ cls._name_cache.set(name, shop.id)
+ else:
+ shop = cls(shop_id)
+ return shop
+
+ @classmethod
+ def write(cls, *args):
+ cls._name_cache.clear()
+ super().write(*args)
+
+ @classmethod
+ def delete(cls, shops):
+ cls._name_cache.clear()
+ super().delete(shops)
+
+ @property
+ def to_sync(self):
+ return False
+
+ def _customer_taxe_rule(self):
+ pool = Pool()
+ Configuration = pool.get('account.configuration')
+ config = Configuration(1)
+ return config.get_multivalue(
+ 'default_customer_tax_rule', company=self.company.id)
+
+ def get_context(self):
+ pool = Pool()
+ Date = pool.get('ir.date')
+ today = Date.today()
+ return {
+ 'language': self.language.code if self.language else None,
+ 'company': self.company.id,
+ 'currency': self.currency.id,
+ 'locations': [w.id for w in self.warehouses],
+ 'stock_date_end': today,
+ 'stock_assign': True,
+ }
+
+ def get_products(self, pattern=None):
+ "Return the list of products with corresponding prices and taxes"
+ pool = Pool()
+ Product = pool.get('product.product')
+ Tax = pool.get('account.tax')
+ if pattern is None:
+ pattern = {}
+
+ with Transaction().set_context(**self.get_context()):
+ all_products = Product.browse(self.products)
+
+ customer_tax_rule = self._customer_taxe_rule()
+ taxes2products = defaultdict(list)
+ for product in all_products:
+ taxes = set()
+ for tax in product.customer_taxes_used:
+ if customer_tax_rule:
+ tax_ids = customer_tax_rule.apply(tax, pattern)
+ if tax_ids:
+ taxes.update(tax_ids)
+ continue
+ taxes.add(tax.id)
+ if customer_tax_rule:
+ tax_ids = customer_tax_rule.apply(None, pattern)
+ if tax_ids:
+ taxes.update(tax_ids)
+ taxes2products[tuple(sorted(taxes))].append(product)
+
+ prices, taxes = {}, {}
+ for tax_ids, products in taxes2products.items():
+ with Transaction().set_context(**self.get_context()):
+ products = Product.browse(products)
+ taxes_ = Tax.browse(tax_ids)
+ with Transaction().set_context(taxes=tax_ids):
+ prices.update(Product.get_sale_price(products))
+ for product in products:
+ taxes[product.id] = sum(
+ t['amount']
+ for t in Tax.compute(taxes_, prices[product.id], 1))
+
+ return all_products, prices, taxes
+
+ def get_categories(self):
+ "Return the list of categories"
+ pool = Pool()
+ Category = pool.get('product.category')
+ with Transaction().set_context(**self.get_context()):
+ return Category.browse(self.categories)
+
+ def get_sale(self, party=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ if not party:
+ party = self.guest_party
+ sale = Sale(party=party)
+ sale.company = self.company
+ sale.currency = self.currency
+ sale.warehouse = self.warehouse
+ sale.invoice_method = 'order'
+ sale.shipment_method = 'order'
+ sale.web_shop = self
+ sale.on_change_party()
+ sale.on_change_web_shop()
+ return sale
+
+
+class Shop_Warehouse(ModelSQL):
+ "Web Shop - Warehouse"
+ __name__ = 'web.shop-stock.location'
+
+ shop = fields.Many2One(
+ 'web.shop', "Shop", ondelete='CASCADE', required=True)
+ warehouse = fields.Many2One(
+ 'stock.location', "Warehouse", ondelete='CASCADE', required=True,
+ domain=[
+ ('type', '=', 'warehouse'),
+ ])
+
+
+class Shop_Product(Inactivate):
+ "Web Shop - Product"
+ __name__ = 'web.shop-product.product'
+
+ shop = fields.Many2One(
+ 'web.shop', "Shop", ondelete='CASCADE', required=True)
+ product = fields.Many2One(
+ 'product.product', "Product",
+ ondelete='RESTRICT', select=True, required=True)
+
+
+class Shop_ProductCategory(Inactivate):
+ "Web Shop - Product Category"
+ __name__ = 'web.shop-product.category'
+
+ shop = fields.Many2One(
+ 'web.shop', "Shop", ondelete='CASCADE', required=True)
+ category = fields.Many2One(
+ 'product.category', "Category",
+ ondelete='RESTRICT', select=True, required=True)
+
+
+class ShopAttribute(metaclass=PoolMeta):
+ __name__ = 'web.shop'
+
+ attributes = fields.Many2Many(
+ 'web.shop-product.attribute', 'shop', 'attribute', "Attributes",
+ help="The list of attributes to publish.")
+ attributes_removed = Many2ManyInactive(
+ 'web.shop-product.attribute', 'shop', 'attribute',
+ "Attributes Removed",
+ help="The list of attributes to unpublish.")
+
+ def get_attributes(self):
+ "Return the list of attributes"
+ pool = Pool()
+ Attribute = pool.get('product.attribute')
+ with Transaction().set_context(**self.get_context()):
+ return Attribute.browse(self.attributes)
+
+
+class Shop_Attribute(Inactivate):
+ "Web Shop - Attribute"
+ __name__ = 'web.shop-product.attribute'
+
+ shop = fields.Many2One(
+ 'web.shop', "Shop", ondelete='CASCADE', required=True)
+ attribute = fields.Many2One(
+ 'product.attribute', "Attribute",
+ ondelete='RESTRICT', select=True, required=True)
+
+
+class User(metaclass=PoolMeta):
+ __name__ = 'web.user'
+
+ invoice_address = fields.Many2One(
+ 'party.address', "Invoice Address",
+ domain=['OR',
+ ('party', '=', Eval('party', -1)),
+ ('party', 'in', Eval('secondary_parties', [])),
+ ],
+ depends=['party', 'secondary_parties'])
+ shipment_address = fields.Many2One(
+ 'party.address', "Shipment Address",
+ domain=['OR',
+ ('party', '=', Eval('party', -1)),
+ ('party', 'in', Eval('secondary_parties', [])),
+ ],
+ depends=['party', 'secondary_parties'])
diff --git a/modules/web_shop/web.xml b/modules/web_shop/web.xml
new file mode 100644
index 0000000000..e765cabe76
--- /dev/null
+++ b/modules/web_shop/web.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+ web.shop
+ form
+ shop_form
+
+
+
+ web.shop
+ tree
+ shop_list
+
+
+
+ Web Shops
+ web.shop
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ User in companies
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ web.shop
+
+ shop_attribute_form
+
+
+
+
+
+ web.user
+
+ user_form
+
+
+
diff --git a/modules/web_shop_vue_storefront/.drone.yml b/modules/web_shop_vue_storefront/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/web_shop_vue_storefront/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/web_shop_vue_storefront/.flake8 b/modules/web_shop_vue_storefront/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/web_shop_vue_storefront/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/web_shop_vue_storefront/CHANGELOG b/modules/web_shop_vue_storefront/CHANGELOG
new file mode 100644
index 0000000000..ed071061c2
--- /dev/null
+++ b/modules/web_shop_vue_storefront/CHANGELOG
@@ -0,0 +1,20 @@
+Version 6.0.5 - 2022-04-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.4 - 2022-01-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.3 - 2021-09-16
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.2 - 2021-09-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.1 - 2021-05-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Initial release
\ No newline at end of file
diff --git a/modules/web_shop_vue_storefront/COPYRIGHT b/modules/web_shop_vue_storefront/COPYRIGHT
new file mode 100644
index 0000000000..d0b4a3655d
--- /dev/null
+++ b/modules/web_shop_vue_storefront/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2020-2022 B2CK
+Copyright (C) 2020-2022 Cédric Krier
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/web_shop_vue_storefront/LICENSE b/modules/web_shop_vue_storefront/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/web_shop_vue_storefront/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/web_shop_vue_storefront/MANIFEST.in b/modules/web_shop_vue_storefront/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/web_shop_vue_storefront/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/web_shop_vue_storefront/README.rst b/modules/web_shop_vue_storefront/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/web_shop_vue_storefront/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/web_shop_vue_storefront/__init__.py b/modules/web_shop_vue_storefront/__init__.py
new file mode 100644
index 0000000000..e8f61953ec
--- /dev/null
+++ b/modules/web_shop_vue_storefront/__init__.py
@@ -0,0 +1,50 @@
+# 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 routes
+
+__all__ = ['register', 'routes']
+
+from . import carrier
+from . import ir
+from . import party
+from . import product
+from . import sale
+from . import web
+
+
+def register():
+ Pool.register(
+ web.Shop,
+ web.ShopVSFIdentifier,
+ web.User,
+ ir.Cron,
+ party.Address,
+ party.Identifier,
+ product.Product,
+ product.Template,
+ product.Category,
+ sale.Sale,
+ sale.Line,
+ module='web_shop_vue_storefront', type_='model')
+ Pool.register(
+ product.ProductAttribute,
+ product.TemplateAttribute,
+ product.Attribute,
+ sale.LineAttribute,
+ module='web_shop_vue_storefront', type_='model',
+ depends=['product_attribute'])
+ Pool.register(
+ web.ShopCoupon,
+ module='web_shop_vue_storefront', type_='model',
+ depends=['sale_promotion_coupon'])
+ Pool.register(
+ carrier.Carrier,
+ module='web_shop_vue_storefront', type_='model',
+ depends=['carrier'])
+ Pool.register(
+ web.ShopShipmentCost,
+ sale.SaleShipmentCost,
+ module='web_shop_vue_storefront', type_='model',
+ depends=['sale_shipment_cost'])
diff --git a/modules/web_shop_vue_storefront/carrier.py b/modules/web_shop_vue_storefront/carrier.py
new file mode 100644
index 0000000000..cfc7126ab0
--- /dev/null
+++ b/modules/web_shop_vue_storefront/carrier.py
@@ -0,0 +1,23 @@
+# 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 PoolMeta
+
+
+class Carrier(metaclass=PoolMeta):
+ __name__ = 'carrier'
+
+ @property
+ def vsf_carrier_title(self):
+ return self.party.name
+
+ @property
+ def vsf_method_title(self):
+ return self.carrier_product.name
+
+ def get_vsf(self):
+ return {
+ 'carrier_code': str(self.id),
+ 'method_code': str(self.id),
+ 'carrier_title': self.vsf_carrier_title,
+ 'method_title': self.vsf_method_title,
+ }
diff --git a/modules/web_shop_vue_storefront/doc/index.rst b/modules/web_shop_vue_storefront/doc/index.rst
new file mode 100644
index 0000000000..62bc1435bf
--- /dev/null
+++ b/modules/web_shop_vue_storefront/doc/index.rst
@@ -0,0 +1,17 @@
+Web Vue Storefront Module
+#########################
+
+The web_shop_vue_storefront module provides the back-end to integrate with `Vue
+Storefront`_ 1.x.
+
+.. _`Vue Storefront`: https://www.vuestorefront.io/
+
+
+Vue StoreFront Configuration
+----------------------------
+
+The endpoint must be updated to use the Tryton URL. You must just replace
+`/api` by
+`http(s)://://web_shop_vue_storefront/`.
+
+The configuration `autoRefreshTokens` must be set to `false`.
diff --git a/modules/web_shop_vue_storefront/exceptions.py b/modules/web_shop_vue_storefront/exceptions.py
new file mode 100644
index 0000000000..203dd8c229
--- /dev/null
+++ b/modules/web_shop_vue_storefront/exceptions.py
@@ -0,0 +1,16 @@
+# 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 werkzeug import exceptions
+
+
+class LoginException(exceptions.Unauthorized):
+ pass
+
+
+class NotFound(exceptions.NotFound):
+ pass
+
+
+class BadRequest(exceptions.BadRequest):
+ pass
diff --git a/modules/web_shop_vue_storefront/ir.py b/modules/web_shop_vue_storefront/ir.py
new file mode 100644
index 0000000000..f655ed7617
--- /dev/null
+++ b/modules/web_shop_vue_storefront/ir.py
@@ -0,0 +1,14 @@
+# 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 PoolMeta
+
+
+class Cron(metaclass=PoolMeta):
+ __name__ = 'ir.cron'
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.method.selection.extend([
+ ('web.shop|vsf_update', "Update Vue Storefront"),
+ ])
diff --git a/modules/web_shop_vue_storefront/locale/bg.po b/modules/web_shop_vue_storefront/locale/bg.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/bg.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/ca.po b/modules/web_shop_vue_storefront/locale/ca.po
new file mode 100644
index 0000000000..71c018d12e
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/ca.po
@@ -0,0 +1,81 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr "Telèfon"
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr "Index Elasticsearch"
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr "URL Elasticsearch"
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr "Registre"
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr "Gran total"
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr "El indentificador Vue Storefront ha de ser únic per a cada registre."
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr "El númbero \"%(code)s\" no és un identificador fiscal vàlid."
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+"No us heu identificat correctament o el vostre compte s'ha suspes "
+"temporalment."
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr "Enviament"
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr "Subtotal"
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr "Impost"
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr "El codi de país \"%(code)s\" és desconegut."
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr "Identificador Vue Storefront"
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr "Identificador tenda web Vue Storefront"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr "Actualitza Vue Storefront"
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
diff --git a/modules/web_shop_vue_storefront/locale/cs.po b/modules/web_shop_vue_storefront/locale/cs.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/cs.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/de.po b/modules/web_shop_vue_storefront/locale/de.po
new file mode 100644
index 0000000000..5bb0d11aec
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/de.po
@@ -0,0 +1,82 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr "Telefon"
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr "Elasticsearch Index"
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr "Elasticsearch URL"
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr "Datensatz"
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr "Gesamtbetrag"
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+"Der Vue Storefront Identifikator muss für jeden Datensatz eindeutig sein."
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr "Die Nummer \"%(code)s\" ist kein gültiger Steueridentifikator."
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+"Sie haben sich nicht korrekt angemeldet oder Ihr Konto ist vorübergehend "
+"deaktiviert."
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr "Versand"
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr "Zwischensumme"
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr "Steuer"
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr "Der Ländercode \"%(code)s\" ist unbekannt."
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr "Vue Storefront Identifikator"
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr "Webshop Vue Storefront Identifikator"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr "Vue Storefront aktualisieren"
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
diff --git a/modules/web_shop_vue_storefront/locale/es.po b/modules/web_shop_vue_storefront/locale/es.po
new file mode 100644
index 0000000000..c98662e1fd
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/es.po
@@ -0,0 +1,81 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr "Teléfono"
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr "Índice Elasticsearch"
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr "URL Elasticsearch"
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr "Registro"
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr "Gran Total"
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr "El identificador Vue Storefront debe ser único por cada registro."
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr "El número \"%(code)s\" no es un identificador fiscal válido."
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+"No te has identificado correctamente o tu cuenta ha sido temporalmente "
+"suspendida."
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr "Envío"
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr "Subtotal"
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr "Impuesto"
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr "El código de país \"%(code)s\" es desconocido."
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr "Identificador Vue Storefront"
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr "Identificador de la tienda Vue Storefront"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr "Actualizar Vue Storefront"
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
diff --git a/modules/web_shop_vue_storefront/locale/es_419.po b/modules/web_shop_vue_storefront/locale/es_419.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/es_419.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/et.po b/modules/web_shop_vue_storefront/locale/et.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/et.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/fa.po b/modules/web_shop_vue_storefront/locale/fa.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/fa.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/fi.po b/modules/web_shop_vue_storefront/locale/fi.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/fi.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/fr.po b/modules/web_shop_vue_storefront/locale/fr.po
new file mode 100644
index 0000000000..0b4e0d62db
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/fr.po
@@ -0,0 +1,82 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr "Téléphone"
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr "UVC"
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr "UVC"
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr "Index Elasticsearch"
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr "URL d'Elasticsearch"
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr "Enregistrement"
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr "Somme finale"
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+"L'identifiant Vue Storefront doit être unique pour chaque enregistrement."
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr "Le numéro « %(code)s » n'est pas un identifiant fiscal valide."
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+"Vous ne vous êtes pas connecté correctement ou votre compte est "
+"temporairement désactivé."
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr "Livraison"
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr "Sous-total"
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr "Taxe"
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr "Le code pays « %(code)s » n'est pas connu."
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr "Identifiant Vue Storefront"
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr "Identifiant de la boutique Web Vue Storefront"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr "Mettre à jour Vue Storefront"
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
diff --git a/modules/web_shop_vue_storefront/locale/hu.po b/modules/web_shop_vue_storefront/locale/hu.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/hu.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/id.po b/modules/web_shop_vue_storefront/locale/id.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/id.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/it.po b/modules/web_shop_vue_storefront/locale/it.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/it.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/lo.po b/modules/web_shop_vue_storefront/locale/lo.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/lo.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/lt.po b/modules/web_shop_vue_storefront/locale/lt.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/lt.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/nl.po b/modules/web_shop_vue_storefront/locale/nl.po
new file mode 100644
index 0000000000..6f5c09a0d1
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/nl.po
@@ -0,0 +1,81 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr "Telefoon"
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr "SKU"
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr "Elasticsearch-index"
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr "Elasticsearch-URL"
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr "Record"
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr "Eindtotaal"
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr "De Vue Storefront-ID moet uniek zijn voor elk record."
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr "Het nummer \"%(code)s\" is geen geldig belastingnummer."
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+"U heeft zich niet correct aangemeld of uw account is tijdelijk "
+"uitgeschakeld."
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr "Verzenden"
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr "Subtotaal"
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr "Belasting"
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr "De landcode \"%(code)s\" is niet gekend."
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr "Vue Storefront-identificatie"
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr "Webwinkel Vue Storefront-identificatie"
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr "Update Vue Storefront"
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr "Vue Storefront"
diff --git a/modules/web_shop_vue_storefront/locale/pl.po b/modules/web_shop_vue_storefront/locale/pl.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/pl.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/pt.po b/modules/web_shop_vue_storefront/locale/pt.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/pt.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/ro.po b/modules/web_shop_vue_storefront/locale/ro.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/ro.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/ru.po b/modules/web_shop_vue_storefront/locale/ru.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/ru.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/sl.po b/modules/web_shop_vue_storefront/locale/sl.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/sl.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/tr.po b/modules/web_shop_vue_storefront/locale/tr.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/tr.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/locale/zh_CN.po b/modules/web_shop_vue_storefront/locale/zh_CN.po
new file mode 100644
index 0000000000..7ddd0525aa
--- /dev/null
+++ b/modules/web_shop_vue_storefront/locale/zh_CN.po
@@ -0,0 +1,79 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:party.address,vsf_telephone:"
+msgid "Telephone"
+msgstr ""
+
+msgctxt "field:product.product,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:product.template,vsf_sku:"
+msgid "SKU"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_index:"
+msgid "Elasticsearch Index"
+msgstr ""
+
+msgctxt "field:web.shop,vsf_elasticsearch_url:"
+msgid "Elasticsearch URL"
+msgstr ""
+
+msgctxt "field:web.shop.vsf_identifier,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_grand_total"
+msgid "Grand Total"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_identifier_record_unique"
+msgid "The Vue Storefront identifier must be unique for each record."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_invalid_tax_identifier"
+msgid "The number \"%(code)s\" is not a valid tax identifier."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_login_wrong"
+msgid "You did not sign in correctly or your account is temporarily disabled."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_shipping"
+msgid "Shipping"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_subtotal"
+msgid "Subtotal"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_tax"
+msgid "Tax"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_unknown_country_code"
+msgid "The country code \"%(code)s\" is not known."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_vsf_identifier"
+msgid "Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "model:web.shop.vsf_identifier,name:"
+msgid "Web Shop Vue Storefront Identifier"
+msgstr ""
+
+msgctxt "selection:ir.cron,method:"
+msgid "Update Vue Storefront"
+msgstr ""
+
+msgctxt "selection:web.shop,type:"
+msgid "Vue Storefront"
+msgstr ""
+
+msgctxt "view:web.shop:"
+msgid "Vue Storefront"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront/message.xml b/modules/web_shop_vue_storefront/message.xml
new file mode 100644
index 0000000000..5ec7a40b7d
--- /dev/null
+++ b/modules/web_shop_vue_storefront/message.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+ The Vue Storefront identifier must be unique for each record.
+
+
+ Vue Storefront Identifier
+
+
+ You did not sign in correctly or your account is temporarily disabled.
+
+
+ The number "%(code)s" is not a valid tax identifier.
+
+
+ The country code "%(code)s" is not known.
+
+
+ Subtotal
+
+
+ Tax
+
+
+ Grand Total
+
+
+ Shipping
+
+
+
diff --git a/modules/web_shop_vue_storefront/party.py b/modules/web_shop_vue_storefront/party.py
new file mode 100644
index 0000000000..eac0d46e57
--- /dev/null
+++ b/modules/web_shop_vue_storefront/party.py
@@ -0,0 +1,144 @@
+# 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 stdnum import get_cc_module
+try:
+ import phonenumbers
+ from phonenumbers import PhoneNumberFormat, NumberParseException
+except ImportError:
+ phonenumbers = None
+
+from trytond.i18n import gettext
+from trytond.model import fields
+from trytond.pool import PoolMeta, Pool
+from trytond.pyson import Eval
+
+from .exceptions import BadRequest
+from .web import split_name, join_name
+
+
+def remove_forbidden_chars(value):
+ from trytond.model.fields import Char
+ if value is None:
+ return value
+ for c in Char.forbidden_chars:
+ if c in value:
+ value = value.replace(c, ' ')
+ return value.strip()
+
+
+class Address(metaclass=PoolMeta):
+ __name__ = 'party.address'
+
+ vsf_telephone = fields.Many2One(
+ 'party.contact_mechanism', "Telephone",
+ domain=[
+ ('party', '=', Eval('party', -1)),
+ ('type', 'in', ['phone', 'mobile']),
+ ],
+ depends=['party'])
+
+ def get_vsf(self, for_party=None):
+ if for_party and for_party != self.party:
+ firstname, lastname = split_name(
+ self.party_name or for_party.name)
+ else:
+ firstname, lastname = split_name(
+ self.party_name or self.party.name)
+ address = {
+ 'id': self.id,
+ 'firstname': firstname,
+ 'lastname': lastname,
+ 'street': self.street.splitlines(),
+ 'city': self.city,
+ 'country_id': self.country.code if self.country else None,
+ 'postcode': self.postal_code,
+ }
+ if self.subdivision:
+ address['region'] = {
+ 'region': self.subdivision.name,
+ }
+ if self.vsf_telephone:
+ address['telephone'] = self.vsf_telephone.value
+ if for_party:
+ address['company'] = self.party.name
+ address['vat_id'] = (
+ self.party.tax_identifier.code
+ if self.party.tax_identifier else None)
+ return address
+
+ def set_vsf(self, data, for_party=None):
+ pool = Pool()
+ Country = pool.get('country.country')
+ Subdivision = pool.get('country.subdivision')
+ ContactMechanism = pool.get('party.contact_mechanism')
+
+ name = remove_forbidden_chars(
+ join_name(data['firstname'], data['lastname']))
+ party = for_party or self.party
+ if name != party.name:
+ self.party_name = name
+ self.street = '\n'.join(map(str, data['street']))
+ self.city = remove_forbidden_chars(data['city'])
+ if data['country_id']:
+ try:
+ self.country, = Country.search([
+ ('code', '=', data['country_id']),
+ ], limit=1)
+ except ValueError:
+ raise BadRequest(gettext(
+ 'web_shop_vue_storefront.msg_unknown_country_code',
+ code=data['country_id']))
+ self.postal_code = data['postcode']
+
+ if data.get('region') and data['region']['region']:
+ domain = [
+ ('name', '=', data['region']['region']),
+ ]
+ if self.country:
+ domain.append(('country', '=', self.country.id))
+ subdivisions = Subdivision.search(domain, limit=1)
+ if subdivisions:
+ self.subdivision, = subdivisions
+
+ if data.get('telephone'):
+ value = remove_forbidden_chars(data['telephone'])
+ if phonenumbers:
+ try:
+ phonenumber = phonenumbers.parse(
+ data['telephone'], data['country_id'])
+ value = phonenumbers.format_number(
+ phonenumber, PhoneNumberFormat.INTERNATIONAL)
+ except NumberParseException:
+ pass
+ contacts = ContactMechanism.search([
+ ('party', '=', self.party.id),
+ ('type', 'in', ['phone', 'mobile']),
+ ('value', '=', value),
+ ], limit=1)
+ if contacts:
+ self.vsf_telephone, = contacts
+ else:
+ contact = ContactMechanism(
+ party=self.party,
+ type='phone',
+ value=value)
+ contact.save()
+ self.vsf_telephone = contact
+
+
+class Identifier(metaclass=PoolMeta):
+ __name__ = 'party.identifier'
+
+ def set_vsf_tax_identifier(self, code):
+ pool = Pool()
+ Party = pool.get('party.party')
+ for type in Party.tax_identifier_types():
+ module = get_cc_module(*type.split('_', 1))
+ if module and module.is_valid(code):
+ self.type = type
+ self.code = code
+ break
+ else:
+ raise BadRequest(gettext(
+ 'web_shop_vue_storefront.msg_invalid_tax_identifier',
+ code=code))
diff --git a/modules/web_shop_vue_storefront/product.py b/modules/web_shop_vue_storefront/product.py
new file mode 100644
index 0000000000..e557c763d6
--- /dev/null
+++ b/modules/web_shop_vue_storefront/product.py
@@ -0,0 +1,256 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import json
+from operator import attrgetter
+
+from trytond.model import fields
+from trytond.pool import PoolMeta, Pool
+from trytond.tools import slugify
+
+from .web import ShopVSFIdentifierMixin
+
+
+class _ProductEntityMixin:
+
+ vsf_sku = fields.Function(
+ fields.Char("SKU"), 'get_vsf_sku', searcher='search_vsf_sku')
+
+ def get_vsf_sku(self, name):
+ return self.code
+
+ @classmethod
+ def search_vsf_sku(cls, name, clause):
+ return [('code',) + tuple(clause[1:])]
+
+ @property
+ def vsf_image(self):
+ if self.vsf_sku:
+ return '/product/%(sku)s.jpg' % {
+ 'sku': slugify(self.vsf_sku.lower()),
+ }
+
+ @property
+ def vsf_type_id(self):
+ return 'virtual' if self.type == 'service' else 'simple'
+
+ @property
+ def vsf_quantity(self):
+ pool = Pool()
+ Uom = pool.get('product.uom')
+ quantity = self.forecast_quantity
+ if quantity < 0:
+ quantity = 0
+ return Uom.compute_qty(self.default_uom, quantity, self.sale_uom)
+
+ def get_vsf_entity(self, shop, price, tax):
+ categories = [
+ c for c in self.categories_all
+ if c in shop.categories]
+ status = 1
+ if not self.salable:
+ status = 2
+ quantity = self.vsf_quantity
+ if quantity <= 0:
+ status = 3
+ return {
+ 'id': self.vsf_identifier.id,
+ 'name': self.name,
+ 'image': self.vsf_image,
+ 'sku': self.vsf_sku,
+ 'url_key': slugify(self.name.lower()),
+ 'url_path': '%s/%s' % (
+ self.vsf_identifier.id, slugify(self.name.lower())),
+ 'type_id': self.vsf_type_id,
+ 'price': float(price),
+ 'price_tax': float(tax),
+ 'price_incl_tax': float(price + tax),
+ 'status': status,
+ 'visibility': 4,
+ 'category_ids': [c.vsf_identifier.id for c in categories],
+ 'category': [c.get_vsf_entity_product(shop) for c in categories],
+ 'stock': [{
+ 'is_in_stock': quantity > 0,
+ 'qty': quantity,
+ }],
+ }
+
+ def get_vsf_stock(self):
+ quantity = self.vsf_quantity
+ return {
+ 'product_id': self.vsf_identifier.id,
+ 'qty': quantity,
+ 'is_in_stock': quantity > 0,
+ }
+
+
+class Product(_ProductEntityMixin, ShopVSFIdentifierMixin, metaclass=PoolMeta):
+ __name__ = 'product.product'
+
+ def vsf_is_configurable(self, shop):
+ return False
+
+
+class Template(
+ _ProductEntityMixin, ShopVSFIdentifierMixin, metaclass=PoolMeta):
+ __name__ = 'product.template'
+
+ @property
+ def vsf_type_id(self):
+ return 'configurable'
+
+ def get_vsf_products(self, shop):
+ return [p for p in self.products if shop in p.web_shops]
+
+
+class Category(ShopVSFIdentifierMixin, metaclass=PoolMeta):
+ __name__ = 'product.category'
+
+ def get_vsf_entity(self, shop):
+ pool = Pool()
+ Product = pool.get('product.product')
+ count = Product.search([
+ ('categories_all', '=', self.id),
+ ('web_shops', '=', shop.id),
+ ], count=True)
+ paths = self.get_vsf_paths(shop)
+
+ def children(category):
+ return [{
+ 'id': c.vsf_identifier.id,
+ 'children_data': children(c),
+ } for c in category.childs
+ if c in shop.categories]
+ return {
+ 'id': self.vsf_identifier.id,
+ 'name': self.name,
+ 'parent_id': (self.parent.vsf_identifier.id
+ if self.parent in shop.categories else None),
+ 'path': '/'.join([str(p.vsf_identifier.id) for p in paths]),
+ 'url_key': slugify(self.name.lower()),
+ 'url_path': '/'.join(
+ map(slugify, map(str.lower, map(attrgetter('name'), paths)))),
+ 'is_active': True,
+ 'position': self.id,
+ 'level': len(paths),
+ 'product_count': count,
+ 'children_data': children(self),
+ }
+
+ def get_vsf_entity_product(self, shop):
+ return {
+ 'category_id': self.vsf_identifier.id,
+ 'name': self.name,
+ 'slug': slugify(self.name),
+ 'path': '/'.join(
+ map(slugify, map(attrgetter('name'),
+ self.get_vsf_paths(shop)))),
+ }
+
+ def get_vsf_paths(self, shop):
+ category = self
+ paths = [category]
+ while category.parent:
+ if category.parent not in shop.categories:
+ break
+ category = category.parent
+ paths.append(category)
+ return list(reversed(paths))
+
+
+class ProductAttribute(metaclass=PoolMeta):
+ __name__ = 'product.product'
+
+ def get_vsf_entity(self, shop, price, tax):
+ entity = super().get_vsf_entity(shop, price, tax)
+ if self.attribute_set:
+ for attribute in self.attribute_set.attributes:
+ if attribute not in shop.attributes:
+ continue
+ name = attribute.name
+ value = self.attributes.get(name)
+ options = {
+ o['name']: o['value']
+ for o in attribute.get_vsf_options()}
+ entity[name] = options.get(value, value)
+ return entity
+
+ def vsf_is_configurable(self, shop):
+ configurable = super().vsf_is_configurable(shop)
+ if self.template.attribute_set:
+ template_products = self.template.get_vsf_products(shop)
+ if len(template_products) > 1:
+ names = [
+ a.name for a in self.template.attribute_set.attributes
+ if a in shop.attributes]
+ values = {filter(
+ lambda k: k in names, sorted(p.attributes.keys()))
+ for p in template_products}
+ configurable = len(values) == len(template_products)
+ return configurable
+
+
+class TemplateAttribute(metaclass=PoolMeta):
+ __name__ = 'product.template'
+
+ def get_vsf_entity(self, shop, price, tax):
+ entity = super().get_vsf_entity(shop, price, tax)
+ if self.attribute_set:
+ for attribute in self.attribute_set.attributes:
+ if attribute not in shop.attributes:
+ continue
+ name = attribute.name
+ p_attr_values = {
+ p.attributes[name]
+ for p in self.products
+ if name in p.attributes}
+ if attribute.type_ == 'multiselection':
+ p_attr_values = set(sum(p_attr_values, []))
+ options = [
+ o['value'] for o in attribute.get_vsf_options()
+ if o['name'] in p_attr_values]
+ entity[name + '_options'] = options
+
+ config_options = entity.setdefault('configurable_options', [])
+ config_options.append(
+ attribute.get_vsf_entity_template(self, p_attr_values))
+ return entity
+
+
+class Attribute(ShopVSFIdentifierMixin, metaclass=PoolMeta):
+ __name__ = 'product.attribute'
+
+ def get_vsf_options(self):
+ return [{
+ 'value': i,
+ 'name': n,
+ 'label': s,
+ } for i, (n, s) in enumerate(
+ json.loads(self.selection_json), 1)]
+
+ def get_vsf_entity(self, shop):
+ return {
+ 'id': self.vsf_identifier.id,
+ 'attribute_id': self.vsf_identifier.id,
+ 'attribute_code': self.name,
+ 'frontend_input': self.type_,
+ 'frontend_label': self.string,
+ 'is_comparable': True,
+ 'is_user_defined': True,
+ 'is_visible': True,
+ 'is_visible_on_front': True,
+ 'options': self.get_vsf_options(),
+ }
+
+ def get_vsf_entity_template(self, template, attribute_values):
+ values = [{
+ 'value_index': o['value'],
+ 'label': o['label'],
+ } for o in self.get_vsf_options()
+ if o['name'] in attribute_values]
+ return {
+ 'attribute_id': self.vsf_identifier.id,
+ 'attribute_code': self.name,
+ 'label': self.string,
+ 'product_id': template.vsf_identifier.id,
+ 'values': values,
+ }
diff --git a/modules/web_shop_vue_storefront/routes.py b/modules/web_shop_vue_storefront/routes.py
new file mode 100644
index 0000000000..31bddce073
--- /dev/null
+++ b/modules/web_shop_vue_storefront/routes.py
@@ -0,0 +1,55 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import logging
+
+from werkzeug.exceptions import HTTPException
+
+from trytond import backend
+from trytond.exceptions import UserError, UserWarning
+from trytond.protocols.wrappers import with_pool, with_transaction
+from trytond.transaction import Transaction
+from trytond.wsgi import app
+
+logger = logging.getLogger(__name__)
+
+
+@app.route(
+ '//web_shop_vue_storefront///',
+ methods={'POST', 'GET'})
+@app.route(
+ '//web_shop_vue_storefront////',
+ methods={'GET'})
+@with_pool
+@with_transaction(context={'_skip_warnings': True})
+def route(request, pool, shop, target, action, sku=None):
+ Shop = pool.get('web.shop')
+ Session = pool.get('web.user.session')
+ shop = Shop.get(shop)
+ method = '_'.join(
+ [request.method, 'vsf', target, action.replace('-', '_')])
+ if 'token' in request.args:
+ with Transaction().new_transaction():
+ try:
+ Session.reset(request.args['token'])
+ except backend.DatabaseOperationalError:
+ logger.debug('Reset session failed', exc_info=True)
+ try:
+ if request.data:
+ data = request.parsed_data
+ else:
+ data = None
+ kwargs = request.args.to_dict()
+ if sku is not None:
+ kwargs['sku'] = sku
+ with Transaction().set_context(**shop.get_context()):
+ result = getattr(shop, method)(data, **kwargs)
+ except HTTPException as exception:
+ Transaction().rollback()
+ return {'code': exception.code, 'result': exception.description}
+ except (UserError, UserWarning) as exception:
+ Transaction().rollback()
+ return {'code': '400', 'result': str(exception)}
+ except Exception as exception:
+ Transaction().rollback()
+ return {'code': 500, 'result': str(exception)}
+ return {'code': 200, 'result': result}
diff --git a/modules/web_shop_vue_storefront/sale.py b/modules/web_shop_vue_storefront/sale.py
new file mode 100644
index 0000000000..025de559db
--- /dev/null
+++ b/modules/web_shop_vue_storefront/sale.py
@@ -0,0 +1,244 @@
+# 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 decimal import Decimal
+
+from trytond.i18n import gettext
+from trytond.pool import PoolMeta, Pool
+
+from .exceptions import BadRequest
+from .web import split_name
+
+
+class Sale(metaclass=PoolMeta):
+ __name__ = 'sale.sale'
+
+ @property
+ def vsf_id(self):
+ if self.web_shop:
+ if self.web_shop.guest_party == self.party:
+ return self.web_id
+ else:
+ return self.id
+
+ def get_vsf_user_order_history(self):
+ firstname, lastname = split_name(self.party.name)
+ return {
+ 'entity_id': self.id,
+ 'increment_id': self.number,
+ 'created_at': self.create_date.isoformat(),
+ 'customer_firstname': firstname,
+ 'customer_lastname': lastname,
+ 'grand_total': float(self.total_amount),
+ 'status': self.state_string,
+ 'items': [
+ line.get_vsf_user_order_history() for line in self.lines],
+ }
+
+ @classmethod
+ def search_vsf(cls, cart_id, shop, user=None):
+ domain = [
+ ('web_shop', '=', shop.id),
+ ('state', '=', 'draft'),
+ ]
+ # cart_id may be either id and web_id
+ cart_id_domain = ['OR']
+ domain.append(cart_id_domain)
+ try:
+ sale_id = int(cart_id)
+ except ValueError:
+ pass
+ else:
+ if user:
+ party = user.party
+ cart_id_domain.append([
+ ('id', '=', sale_id),
+ ('party', '=', party.id),
+ ])
+ cart_id_domain.append(('web_id', '=', cart_id))
+ try:
+ sale, = cls.search(domain, limit=1)
+ except ValueError:
+ raise BadRequest()
+ return sale
+
+ @property
+ def vsf_subtotal(self):
+ return self.untaxed_amount
+
+ def set_vsf(self, data, user=None):
+ if user and self.web_shop and self.party == self.web_shop.guest_party:
+ self.party = user.party
+ self.on_change_party()
+ if user and 'addressInformation' in data:
+ addresses = data['addressInformation']
+ address_data = addresses.get('shippingAddress')
+ if address_data:
+ address = user.set_vsf_address(address_data, self.party)
+ if address.party != self.party:
+ self.shipment_party = address.party
+ self.shipment_address = address
+ address_data = addresses.get('billingAddress')
+ if address_data:
+ if address_data != addresses.get('shippingAddress'):
+ address = user.set_vsf_address(address_data, self.party)
+ if address.party != self.party:
+ self.invoice_party = address.party
+ self.invoice_address = address
+
+ def get_vsf(self):
+ return {
+ 'grand_total': float(self.total_amount),
+ 'items': [line.get_vsf() for line in self.lines if line.product],
+ 'total_segments': [{
+ 'code': 'subtotal',
+ 'title': gettext('web_shop_vue_storefront.msg_subtotal'),
+ 'value': float(self.vsf_subtotal),
+ }, {
+ 'code': 'tax',
+ 'title': gettext('web_shop_vue_storefront.msg_tax'),
+ 'value': float(self.tax_amount),
+ }, {
+ 'code': 'grand_total',
+ 'title': gettext(
+ 'web_shop_vue_storefront.msg_grand_total'),
+ 'value': float(self.total_amount),
+ }],
+ }
+
+
+class SaleShipmentCost(metaclass=PoolMeta):
+ __name__ = 'sale.sale'
+
+ def get_vsf(self):
+ pool = Pool()
+ Tax = pool.get('account.tax')
+ item = super().get_vsf()
+ if self.carrier:
+ cost = self.compute_shipment_cost()
+ cost_line = self.get_shipment_cost_line(cost)
+ taxes = Tax.compute(cost_line.taxes, cost, 1)
+ cost += sum(t['amount'] for t in taxes)
+ cost = float(self.currency.round(cost))
+ item['grand_total'] += cost
+ item['total_segments'].insert(1, {
+ 'code': 'shipping',
+ 'title': gettext('web_shop_vue_storefront.msg_shipping'),
+ 'value': cost,
+ })
+ for segment in item['total_segments']:
+ if segment['code'] == 'grand_total':
+ segment['value'] += cost
+ return item
+
+ def set_vsf_shipping_methods(self, data):
+ pool = Pool()
+ Country = pool.get('country.country')
+ Address = pool.get('party.address')
+ try:
+ country, = Country.search([
+ ('code', '=', data['address']['country_id']),
+ ])
+ except ValueError:
+ raise BadRequest()
+ if not self.shipment_address:
+ self.shipment_address = Address()
+ self.shipment_address.country = country
+
+ def set_vsf(self, data, user):
+ pool = Pool()
+ Carrier = pool.get('carrier')
+ super().set_vsf(data, user)
+ if 'addressInformation' in data:
+ if data['addressInformation']['shipping_carrier_code']:
+ try:
+ carrier_id = int(
+ data['addressInformation']['shipping_carrier_code'])
+ except ValueError:
+ raise BadRequest()
+ try:
+ carrier, = Carrier.search([
+ ('id', '=', carrier_id),
+ ], limit=1)
+ except ValueError:
+ raise BadRequest()
+ else:
+ carrier = None
+ # Use id to get proper context
+ self.carrier = carrier.id if carrier else None
+
+
+class Line(metaclass=PoolMeta):
+ __name__ = 'sale.line'
+
+ @property
+ def vsf_tax_amount(self):
+ return sum(
+ (v['amount'] for v in self._get_taxes().values()), Decimal(0))
+
+ def get_vsf_user_order_history(self):
+ amount_incl_tax = self.amount + self.vsf_tax_amount
+ digits = self.__class__.unit_price.digits
+ price_incl_tax = (
+ amount_incl_tax / Decimal(str(self.quantity))
+ ).quantize(Decimal(1) / 10 ** digits[1])
+ return {
+ 'name': self.product.name if self.product else '',
+ 'sku': self.product.vsf_sku if self.product else '',
+ 'price_incl_tax': float(price_incl_tax),
+ 'qty_ordered': self.quantity,
+ 'row_total_incl_tax': float(amount_incl_tax),
+ }
+
+ def get_vsf(self):
+ assert self.product
+ amount_incl_tax = self.amount + self.vsf_tax_amount
+ digits = self.__class__.unit_price.digits
+ price_incl_tax = (
+ amount_incl_tax / Decimal(str(self.quantity))
+ ).quantize(Decimal(1) / 10 ** digits[1])
+ return {
+ 'item_id': self.id,
+ 'sku': self.product.vsf_sku,
+ 'qty': self.quantity,
+ 'name': self.product.name,
+ 'price': float(price_incl_tax),
+ 'product_type': self.product.vsf_type_id,
+ 'quote_id': self.sale.id,
+ 'product_option': {
+ 'extension_attributes': {
+ },
+ },
+ }
+
+ def set_vsf(self, data):
+ pool = Pool()
+ Product = pool.get('product.product')
+ try:
+ self.product, = Product.search([
+ ('vsf_sku', '=', data['sku']),
+ ], limit=1)
+ except ValueError:
+ raise BadRequest()
+ self.quantity = data['qty']
+ self.on_change_product()
+
+
+class LineAttribute(metaclass=PoolMeta):
+ __name__ = 'sale.line'
+
+ def get_vsf_cart(self):
+ assert self.sale.web_shop
+ item = super().get_vsf_cart()
+ if self.product.attributes_set:
+ product_option = item['product_option']
+ extension_attributes = product_option['extension_attributes']
+ extension_attributes['configurable_item_options'] = options = []
+ for attribute in self.product.attributes_set.attributes:
+ if attribute not in self.sale.web_shop.attributes:
+ continue
+ options.append({
+ 'option_id': attribute.vsf_identifier.id,
+ 'option_value': self.attributes.get(attribute.name),
+ })
+ return item
diff --git a/modules/web_shop_vue_storefront/setup.py b/modules/web_shop_vue_storefront/setup.py
new file mode 100755
index 0000000000..615d642665
--- /dev/null
+++ b/modules/web_shop_vue_storefront/setup.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname, slice=None):
+ content = io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+ if slice:
+ content = '\n'.join(content.splitlines()[slice])
+ return content
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_web_shop_vue_storefront'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = ['elasticsearch', 'python-stdnum >= 1.1.0', 'werkzeug']
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = [
+ get_require_version('proteus'),
+ get_require_version('trytond_product_attribute'),
+ get_require_version('trytond_sale_promotion_coupon'),
+ get_require_version('trytond_sale_shipment_cost'),
+ get_require_version('trytond_carrier'),
+ ]
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to integrate with Vue Storefront',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/web_shop_vue_storefront',
+ },
+ keywords='web vue storefront PWA ecommerce',
+ package_dir={'trytond.modules.web_shop_vue_storefront': '.'},
+ packages=(
+ ['trytond.modules.web_shop_vue_storefront']
+ + ['trytond.modules.web_shop_vue_storefront.%s' % p
+ for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.web_shop_vue_storefront': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.fodt',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ web_shop_vue_storefront = trytond.modules.web_shop_vue_storefront
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/web_shop_vue_storefront/tests/__init__.py b/modules/web_shop_vue_storefront/tests/__init__.py
new file mode 100644
index 0000000000..ddb598e785
--- /dev/null
+++ b/modules/web_shop_vue_storefront/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.web_shop_vue_storefront.tests.test_web_shop_vue_storefront import suite # noqa: E501
+except ImportError:
+ from .test_web_shop_vue_storefront import suite
+
+__all__ = ['suite']
diff --git a/modules/web_shop_vue_storefront/tests/scenario_web_shop_vue_storefront.rst b/modules/web_shop_vue_storefront/tests/scenario_web_shop_vue_storefront.rst
new file mode 100644
index 0000000000..c791a8c4f6
--- /dev/null
+++ b/modules/web_shop_vue_storefront/tests/scenario_web_shop_vue_storefront.rst
@@ -0,0 +1,273 @@
+================================
+Web Shop Vue Storefront Scenario
+================================
+
+Imports::
+
+ >>> from decimal import Decimal
+ >>> from unittest.mock import MagicMock, ANY, patch
+
+ >>> from proteus import Model
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import (
+ ... create_company, get_company)
+ >>> from trytond.modules.account.tests.tools import (
+ ... create_chart, get_accounts)
+
+ >>> from trytond.modules.web_shop_vue_storefront.tests.tools import (
+ ... AnyDictWith)
+
+Patch elasticsearch::
+
+ >>> from trytond.modules.web_shop_vue_storefront import web
+ >>> es = MagicMock()
+ >>> _ = patch.object(
+ ... web, 'VSFElasticsearch', return_value=es).start()
+
+Install web_shop_vue_storefront::
+
+ >>> config = activate_modules(
+ ... ['web_shop_vue_storefront', 'product_attribute'])
+
+Create company::
+
+ >>> Company = Model.get('company.company')
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create chart of accounts::
+
+ >>> _ = create_chart(company)
+ >>> accounts = get_accounts(company)
+
+Define a web shop::
+
+ >>> WebShop = Model.get('web.shop')
+ >>> web_shop = WebShop(name="Web Shop")
+ >>> web_shop.type = 'vsf'
+ >>> web_shop.save()
+
+Create categories::
+
+ >>> Category = Model.get('product.category')
+ >>> category1 = Category(name="Category 1")
+ >>> category1.save()
+ >>> sub_category = Category(name="Sub Category", parent=category1)
+ >>> sub_category.save()
+ >>> category2 = Category(name="Category 2")
+ >>> category2.save()
+
+ >>> account_category = Category(name="Account Category")
+ >>> account_category.accounting = True
+ >>> account_category.account_expense = accounts['expense']
+ >>> account_category.account_revenue = accounts['revenue']
+ >>> account_category.save()
+
+Create attribute set::
+
+ >>> ProductAttributeSet = Model.get('product.attribute.set')
+ >>> ProductAttribute = Model.get('product.attribute')
+ >>> attribute_set = ProductAttributeSet(name="Attributes")
+ >>> attribute = attribute_set.attributes.new()
+ >>> attribute.name = 'attr1'
+ >>> attribute.string = "Attribute 1"
+ >>> attribute.type_ = 'selection'
+ >>> attribute.selection = "opt1:Option1\nopt2:Option2"
+ >>> attribute = attribute_set.attributes.new()
+ >>> attribute.name = 'attr2'
+ >>> attribute.string = "Attribute 1"
+ >>> attribute.type_ = 'boolean'
+ >>> attribute_set.save()
+ >>> attribute1, attribute2 = attribute_set.attributes
+
+Create products::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+ >>> ProductTemplate = Model.get('product.template')
+ >>> Product = Model.get('product.product')
+ >>> template = ProductTemplate()
+ >>> template.name = "Product 1"
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.salable = True
+ >>> template.list_price = Decimal(10)
+ >>> template.account_category = account_category
+ >>> template.categories.append(Category(category1.id))
+ >>> template.categories.append(Category(sub_category.id))
+ >>> template.save()
+ >>> product1, = template.products
+ >>> product1.suffix_code = 'PROD1'
+ >>> product1.save()
+
+ >>> template = ProductTemplate()
+ >>> template.name = "Product 2"
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.salable = True
+ >>> template.list_price = Decimal(20)
+ >>> template.account_category = account_category
+ >>> template.save()
+ >>> product2, = template.products
+ >>> product2.suffix_code = 'PROD2'
+ >>> product2.save()
+
+ >>> configurable = ProductTemplate()
+ >>> configurable.name = "Configurable"
+ >>> configurable.code = "CONF"
+ >>> configurable.default_uom = unit
+ >>> configurable.type = 'goods'
+ >>> configurable.salable = True
+ >>> configurable.list_price = Decimal(50)
+ >>> configurable.attribute_set = attribute_set
+ >>> configurable.account_category = account_category
+ >>> configurable1, = configurable.products
+ >>> configurable1.suffix_code = "1"
+ >>> configurable1.attributes = {
+ ... 'attr1': 'opt1',
+ ... 'attr2': True,
+ ... }
+ >>> configurable2 = configurable.products.new()
+ >>> configurable2.suffix_code = "2"
+ >>> configurable2.attributes = {
+ ... 'attr1': 'opt2',
+ ... 'attr2': True,
+ ... }
+ >>> configurable.save()
+ >>> configurable1, configurable2 = configurable.products
+
+Set categories, products and attributes to web shop::
+
+ >>> web_shop.categories.extend([
+ ... Category(category1.id),
+ ... Category(sub_category.id),
+ ... Category(category2.id)])
+ >>> web_shop.products.extend([
+ ... Product(product1.id),
+ ... Product(product2.id),
+ ... Product(configurable1.id),
+ ... Product(configurable2.id)])
+ >>> web_shop.attributes.extend([
+ ... ProductAttribute(attribute1.id),
+ ... ProductAttribute(attribute2.id)])
+ >>> web_shop.save()
+
+Run VSF update::
+
+ >>> es.reset_mock()
+ >>> Cron = Model.get('ir.cron')
+ >>> cron_sync, = Cron.find([
+ ... ('method', '=', 'web.shop|vsf_update'),
+ ... ])
+ >>> cron_sync.click('run_once')
+ >>> es.index.call_count
+ 8
+ >>> es.index.assert_any_call(
+ ... id=category1.vsf_identifier.id, index='vue_storefront_catalog',
+ ... doc_type='category', body=AnyDictWith({
+ ... 'name': "Category 1",
+ ... 'parent_id': None,
+ ... 'url_key': 'category-1',
+ ... 'url_path': 'category-1',
+ ... 'level': 1,
+ ... 'product_count': 1,
+ ... 'children_data': [AnyDictWith({})],
+ ... }))
+ >>> es.index.assert_any_call(
+ ... id=sub_category.vsf_identifier.id, index='vue_storefront_catalog',
+ ... doc_type='category', body=AnyDictWith({
+ ... 'name': "Sub Category",
+ ... 'parent_id': category1.vsf_identifier.id,
+ ... 'url_key': 'sub-category',
+ ... 'url_path': 'category-1/sub-category',
+ ... 'level': 2,
+ ... 'product_count': 1,
+ ... 'children_data': [],
+ ... }))
+ >>> es.index.assert_any_call(
+ ... id=product1.vsf_identifier.id, index='vue_storefront_catalog',
+ ... doc_type='product', body=AnyDictWith({
+ ... 'name': "Product 1",
+ ... 'image': '/product/prod1.jpg',
+ ... 'sku': 'PROD1',
+ ... 'url_key': 'product-1',
+ ... 'type_id': 'simple',
+ ... 'price': 10,
+ ... 'price_tax': 0,
+ ... 'price_incl_tax': 10,
+ ... 'status': 3,
+ ... 'category_ids': [ANY, ANY],
+ ... 'category': [AnyDictWith({}), AnyDictWith({})],
+ ... 'stock': [{
+ ... 'is_in_stock': False,
+ ... 'qty': 0,
+ ... }],
+ ... }))
+ >>> es.index.assert_any_call(
+ ... id=configurable.vsf_identifier.id, index='vue_storefront_catalog',
+ ... doc_type='product', body=AnyDictWith({
+ ... 'name': "Configurable",
+ ... 'image': '/product/conf.jpg',
+ ... 'sku': 'CONF',
+ ... 'url_key': 'configurable',
+ ... 'type_id': 'configurable',
+ ... 'price': 50,
+ ... 'price_tax': 0,
+ ... 'price_incl_tax': 50,
+ ... 'status': 3,
+ ... 'category_ids': [],
+ ... 'category': [],
+ ... 'stock': [{
+ ... 'is_in_stock': False,
+ ... 'qty': 0,
+ ... }],
+ ... 'attr1_options': [1, 2],
+ ... 'attr2_options': [],
+ ... 'configurable_options': [
+ ... AnyDictWith({
+ ... 'attribute_code': 'attr1',
+ ... 'label': "Attribute 1",
+ ... 'product_id': configurable.vsf_identifier.id,
+ ... 'values': [
+ ... {'value_index': 1, 'label': "Option1"},
+ ... {'value_index': 2, 'label': "Option2"},
+ ... ],
+ ... }),
+ ... AnyDictWith({}),
+ ... ],
+ ... 'configurable_children': [
+ ... AnyDictWith({'sku': 'CONF1'}),
+ ... AnyDictWith({'sku': 'CONF2'}),
+ ... ],
+ ... }))
+ >>> es.index.assert_any_call(
+ ... id=attribute1.vsf_identifier.id, index='vue_storefront_catalog',
+ ... doc_type='attribute', body=AnyDictWith({
+ ... 'attribute_code': 'attr1',
+ ... 'frontend_input': 'selection',
+ ... 'frontend_label': "Attribute 1",
+ ... 'options': [
+ ... {'value': 1, 'name': 'opt1', 'label': "Option1"},
+ ... {'value': 2, 'name': 'opt2', 'label': "Option2"},
+ ... ],
+ ... }))
+
+Remove a category, a product and an attribute::
+
+ >>> _ = web_shop.categories.pop(web_shop.categories.index(category2))
+ >>> _ = web_shop.products.pop(web_shop.products.index(product2))
+ >>> _ = web_shop.attributes.pop(web_shop.attributes.index(attribute2))
+ >>> web_shop.save()
+
+Run VSF update::
+
+ >>> es.reset_mock()
+ >>> Cron = Model.get('ir.cron')
+ >>> cron_sync, = Cron.find([
+ ... ('method', '=', 'web.shop|vsf_update'),
+ ... ])
+ >>> cron_sync.click('run_once')
+ >>> es.index.call_count
+ 5
+ >>> es.delete.call_count
+ 3
diff --git a/modules/web_shop_vue_storefront/tests/test_web_shop_vue_storefront.py b/modules/web_shop_vue_storefront/tests/test_web_shop_vue_storefront.py
new file mode 100644
index 0000000000..724db439b1
--- /dev/null
+++ b/modules/web_shop_vue_storefront/tests/test_web_shop_vue_storefront.py
@@ -0,0 +1,915 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import unittest
+import doctest
+from contextlib import contextmanager
+from decimal import Decimal
+
+from unittest.mock import patch, ANY
+
+from werkzeug.exceptions import Unauthorized
+
+from trytond.pool import Pool
+from trytond.tests.test_tryton import doctest_teardown, doctest_checker
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.tests.test_tryton import suite as test_suite
+from trytond.transaction import Transaction
+from trytond.modules.company.tests import (
+ create_company, set_company, CompanyTestMixin)
+from ..exceptions import LoginException
+
+CUSTOMER = {
+ 'email': 'customer@example.com',
+ 'firstname': 'John',
+ 'lastname': 'Doe',
+ }
+ADDRESS = {
+ 'region': {
+ 'region': None,
+ },
+ 'country_id': "US",
+ 'street': [
+ "Cliff Street",
+ "300"
+ ],
+ 'telephone': '+1 202-555-0148',
+ 'postcode': '18503',
+ 'city': "Scranton",
+ 'firstname': CUSTOMER['firstname'],
+ 'lastname': CUSTOMER['lastname'],
+ }
+ADDRESS_COMPANY = ADDRESS.copy()
+ADDRESS_COMPANY['company'] = 'Saber'
+ADDRESS_COMPANY['vat_id'] = 'BE0500923836'
+
+
+class WebVueStorefrontTestCase(CompanyTestMixin, ModuleTestCase):
+ 'Test Web Shop Vue Storefront module'
+ module = 'web_shop_vue_storefront'
+ extras = [
+ 'product_attribute',
+ 'sale_promotion_coupon',
+ 'sale_shipment_cost',
+ 'carrier',
+ ]
+ maxDiff = None
+
+ @contextmanager
+ def create_web_shop(self):
+ pool = Pool()
+ WebShop = pool.get('web.shop')
+ Location = pool.get('stock.location')
+ Party = pool.get('party.party')
+
+ company = create_company()
+ warehouse, = Location.search([('type', '=', 'warehouse')], limit=1)
+ web_shop = WebShop(name="Web Shop")
+ web_shop.company = company
+ web_shop.currency = company.currency
+ web_shop.warehouses = [warehouse]
+ web_shop.type = 'vsf'
+ web_shop.guest_party = Party(name="Guest")
+ web_shop.save()
+ with Transaction().set_context(**web_shop.get_context()):
+ yield web_shop
+
+ def create_product(self, web_shop, quantity=0, code="CODE"):
+ pool = Pool()
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ Category = pool.get('product.category')
+ Uom = pool.get('product.uom')
+ Location = pool.get('stock.location')
+ Move = pool.get('stock.move')
+
+ unit, = Uom.search([('name', '=', "Unit")], limit=1)
+ template = Template(name="Product")
+ template.type = 'goods'
+ template.list_price = Decimal(100)
+ template.default_uom = unit
+ template.salable = True
+ template.sale_uom = unit
+ template.products = [Product(suffix_code=code, web_shops=[web_shop])]
+ template.account_category = Category(name="Category", accounting=True)
+ template.save()
+ product, = template.products
+
+ if quantity:
+ supplier, = Location.search([('code', '=', 'SUP')])
+ storage, = Location.search([('code', '=', 'STO')])
+ with set_company(web_shop.company):
+ move = Move(product=product)
+ move.uom = unit
+ move.quantity = quantity
+ move.from_location = supplier
+ move.to_location = storage
+ move.unit_price = Decimal(50)
+ move.save()
+ Move.do([move])
+ Product.set_vsf_identifier([product])
+ return product
+
+ def create_coupon(self, code='CODE'):
+ pool = Pool()
+ Promotion = pool.get('sale.promotion')
+ Coupon = pool.get('sale.promotion.coupon')
+
+ promotion = Promotion(name="10%")
+ promotion.formula = '0.9 * unit_price'
+ promotion.coupons = coupon, = [Coupon(name="Promo")]
+ coupon.numbers = [{'number': code}]
+ promotion.save()
+
+ def create_carrier(
+ self, name='Carrier', product_name="Delivery",
+ price=Decimal('10')):
+ pool = Pool()
+ Carrier = pool.get('carrier')
+ Party = pool.get('party.party')
+ Template = pool.get('product.template')
+ Product = pool.get('product.product')
+ Category = pool.get('product.category')
+ Uom = pool.get('product.uom')
+
+ unit, = Uom.search([('name', '=', "Unit")], limit=1)
+ template = Template(name=product_name)
+ template.type = 'service'
+ template.list_price = price
+ template.default_uom = template.sale_uom = unit
+ template.salable = True
+ template.products = [Product()]
+ template.account_category = Category(name="Service", accounting=True)
+ template.save()
+
+ carrier = Carrier()
+ carrier.party = Party(name=name)
+ carrier.carrier_product, = template.products
+ carrier.save()
+ return carrier
+
+ @with_transaction(user=0)
+ def test_user_create(self):
+ pool = Pool()
+ User = pool.get('web.user')
+ with self.create_web_shop() as web_shop:
+ result = web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "SecretPassword"
+ })
+ users = User.search([])
+
+ self.assertEqual(result, {
+ "email": CUSTOMER['email'],
+ "firstname": CUSTOMER['firstname'],
+ "lastname": CUSTOMER['lastname'],
+ "addresses": [],
+ })
+ self.assertEqual(len(users), 1)
+
+ @with_transaction(user=0)
+ def test_user_login(self):
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+
+ result = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+
+ self.assertTrue(result)
+
+ @with_transaction(user=0)
+ def test_user_login_wrong_password(self):
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+
+ with self.assertRaises(LoginException):
+ web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "Bad",
+ })
+
+ @with_transaction(user=0)
+ def test_user_reset_password(self):
+ pool = Pool()
+ User = pool.get('web.user')
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "SecretPassword",
+ })
+
+ with patch.object(User, 'reset_password') as reset_password:
+ result = web_shop.POST_vsf_user_reset_password({
+ "email": CUSTOMER['email'],
+ })
+
+ self.assertFalse(result)
+ reset_password.assert_called_once_with(ANY)
+
+ @with_transaction(user=0)
+ def test_user_change_password(self):
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "OldPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "OldPassword",
+ })
+
+ result = web_shop.POST_vsf_user_change_password({
+ "currentPassword": "OldPassword",
+ "newPassword": "NewPassword",
+ }, token=token)
+
+ self.assertFalse(result)
+
+ @with_transaction(user=0)
+ def test_user_change_password_wrong_token(self):
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "OldPassword",
+ })
+
+ with self.assertRaises(Unauthorized):
+ web_shop.POST_vsf_user_change_password({
+ "currentPassword": "BadPassword",
+ "newPassword": "NewPassword",
+ }, 'wrong token')
+
+ @with_transaction(user=0)
+ def test_user_change_password_wrong_password(self):
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "OldPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "OldPassword",
+ })
+
+ with self.assertRaises(LoginException):
+ web_shop.POST_vsf_user_change_password({
+ "currentPassword": "BadPassword",
+ "newPassword": "NewPassword",
+ }, token=token)
+
+ @with_transaction(user=0)
+ def test_user_order_history(self):
+ pool = Pool()
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+ cart = web_shop.POST_vsf_cart_create(None, token=token)
+ web_shop.POST_vsf_order_create({
+ 'products': [{
+ 'sku': 'CODE',
+ 'qty': 2,
+ }],
+ 'addressInformation': {
+ 'shippingAddress': ADDRESS,
+ 'billingAddress': ADDRESS_COMPANY,
+ 'shipping_method_code': None,
+ 'shipping_carrier_code': None,
+ 'payment_method_code': 'cashondelivery',
+ 'payment_method_additional': {},
+ },
+ }, cart, token=token)
+
+ items = web_shop.GET_vsf_user_order_history(None, token=token)
+
+ self.assertEqual(items, {
+ 'items': [{
+ 'entity_id': ANY,
+ 'increment_id': ANY,
+ 'created_at': ANY,
+ 'customer_firstname': CUSTOMER['firstname'],
+ 'customer_lastname': CUSTOMER['lastname'],
+ 'grand_total': 200,
+ 'status': "Confirmed",
+ 'items': [{
+ 'name': "Product",
+ 'sku': 'CODE',
+ 'price_incl_tax': 100,
+ 'qty_ordered': 2,
+ 'row_total_incl_tax': 200,
+ }],
+ }],
+ })
+
+ @with_transaction(user=0)
+ def test_user_order_history_wrong_token(self):
+ with self.create_web_shop() as web_shop:
+ with self.assertRaises(Unauthorized):
+ web_shop.GET_vsf_user_order_history(None, 'wrong token')
+
+ @with_transaction(user=0)
+ def test_user_me(self):
+ pool = Pool()
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+
+ result = web_shop.GET_vsf_user_me(None, token=token)
+
+ data = {'customer': CUSTOMER.copy()}
+ data['customer']['addresses'] = []
+ self.assertEqual(result, data['customer'])
+
+ data = {'customer': CUSTOMER.copy()}
+ data['customer']['addresses'] = [ADDRESS_COMPANY.copy()]
+ result = web_shop.POST_vsf_user_me(data, token=token)
+
+ data['customer']['addresses'][0]['id'] = ANY
+ data['customer']['addresses'][0].pop('region')
+ self.assertEqual(result, data['customer'])
+
+ @with_transaction(user=0)
+ def test_user_me_wrong_token(self):
+ with self.create_web_shop() as web_shop:
+
+ with self.assertRaises(Unauthorized):
+ web_shop.GET_vsf_user_me(None, 'wrong token')
+
+ with self.assertRaises(Unauthorized):
+ web_shop.POST_vsf_user_me({}, 'wrong token')
+
+ @with_transaction(user=0)
+ def test_user_me_change_address(self):
+ pool = Pool()
+ Address = pool.get('party.address')
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+ data = {'customer': CUSTOMER.copy()}
+ data['customer']['addresses'] = [ADDRESS_COMPANY]
+ result = web_shop.POST_vsf_user_me(data, token=token)
+
+ data = {'customer': CUSTOMER.copy()}
+ address = ADDRESS.copy()
+ address['lastname'] = "Scott"
+ data['customer']['addresses'] = [address]
+ result = web_shop.POST_vsf_user_me(
+ {"customer": result}, token=token)
+
+ inactive_addresses = Address.search([('active', '=', False)])
+ self.assertEqual(len(inactive_addresses), 1)
+
+ @with_transaction(user=0)
+ def test_stock_check(self):
+ with self.create_web_shop() as web_shop:
+ product = self.create_product(web_shop, quantity=10)
+
+ result = web_shop.GET_vsf_stock_check(None, product.vsf_sku)
+
+ self.assertEqual(result, {
+ 'product_id': product.vsf_identifier.id,
+ 'qty': 10,
+ 'is_in_stock': True,
+ })
+
+ @with_transaction(user=0)
+ def test_stock_list(self):
+ with self.create_web_shop() as web_shop:
+ product1 = self.create_product(web_shop, quantity=10)
+ product2 = self.create_product(web_shop, quantity=0, code="CODE2")
+
+ result = web_shop.GET_vsf_stock_list(
+ None, ','.join([p.vsf_sku for p in [product1, product2]]))
+
+ self.assertEqual(result, [{
+ 'product_id': product1.vsf_identifier.id,
+ 'qty': 10,
+ 'is_in_stock': True,
+ }, {
+ 'product_id': product2.vsf_identifier.id,
+ 'qty': 0,
+ 'is_in_stock': False,
+ }])
+
+ @with_transaction(user=0)
+ def test_cart_create_without_token(self):
+ with self.create_web_shop() as web_shop:
+ cart = web_shop.POST_vsf_cart_create(None)
+
+ self.assertIsInstance(cart, str)
+
+ @with_transaction(user=0)
+ def test_cart_create_with_token(self):
+ with self.create_web_shop() as web_shop:
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+
+ cart = web_shop.POST_vsf_cart_create(None, token=token)
+
+ self.assertIsInstance(cart, int)
+
+ @with_transaction(user=0)
+ def test_cart_pull_without_token(self):
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ items = web_shop.GET_vsf_cart_pull(None, cart)
+
+ self.assertEqual(items, [{
+ 'item_id': ANY,
+ 'sku': 'CODE',
+ 'qty': 1,
+ 'name': 'Product',
+ 'price': 100,
+ 'product_type': 'simple',
+ 'quote_id': ANY,
+ 'product_option': {
+ 'extension_attributes': {},
+ },
+ }])
+
+ @with_transaction(user=0)
+ def test_cart_pull_with_token(self):
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+ cart = web_shop.POST_vsf_cart_create(None, token=token)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart, token=token)
+
+ items = web_shop.GET_vsf_cart_pull(None, cart, token=token)
+
+ self.assertEqual(items, [{
+ 'item_id': ANY,
+ 'sku': 'CODE',
+ 'qty': 1,
+ 'name': 'Product',
+ 'price': 100,
+ 'product_type': 'simple',
+ 'quote_id': ANY,
+ 'product_option': {
+ 'extension_attributes': {},
+ },
+ }])
+
+ @with_transaction(user=0)
+ def test_cart_update_without_token(self):
+ pool = Pool()
+ SaleLine = pool.get('sale.line')
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+
+ item = web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ self.assertEqual(item, {
+ 'item_id': ANY,
+ 'sku': 'CODE',
+ 'qty': 1,
+ 'name': 'Product',
+ 'price': 100,
+ 'product_type': 'simple',
+ 'quote_id': ANY,
+ 'product_option': {
+ 'extension_attributes': {},
+ },
+ })
+ sale_lines = SaleLine.search([])
+ self.assertEqual(len(sale_lines), 1)
+
+ @with_transaction(user=0)
+ def test_cart_update_with_token(self):
+ pool = Pool()
+ SaleLine = pool.get('sale.line')
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+ cart = web_shop.POST_vsf_cart_create(None, token=token)
+
+ item = web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart, token=token)
+
+ self.assertEqual(item, {
+ 'item_id': ANY,
+ 'sku': 'CODE',
+ 'qty': 1,
+ 'name': 'Product',
+ 'price': 100,
+ 'product_type': 'simple',
+ 'quote_id': ANY,
+ 'product_option': {
+ 'extension_attributes': {},
+ },
+ })
+ sale_lines = SaleLine.search([])
+ self.assertEqual(len(sale_lines), 1)
+
+ @with_transaction(user=0)
+ def test_cart_update2(self):
+ pool = Pool()
+ SaleLine = pool.get('sale.line')
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ item = web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ item = web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 2,
+ 'item_id': item['item_id'],
+ },
+ }, cart)
+
+ self.assertEqual(item, {
+ 'item_id': ANY,
+ 'sku': 'CODE',
+ 'qty': 2,
+ 'name': 'Product',
+ 'price': 100,
+ 'product_type': 'simple',
+ 'quote_id': ANY,
+ 'product_option': {
+ 'extension_attributes': {},
+ },
+ })
+ sale_lines = SaleLine.search([])
+ self.assertEqual(len(sale_lines), 1)
+
+ @with_transaction(user=0)
+ def test_cart_delete(self):
+ pool = Pool()
+ SaleLine = pool.get('sale.line')
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ item = web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ result = web_shop.POST_vsf_cart_delete({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ 'item_id': item['item_id'],
+ },
+ }, cart)
+
+ self.assertTrue(result)
+ sale_lines = SaleLine.search([])
+ self.assertEqual(len(sale_lines), 0)
+
+ @with_transaction(user=0)
+ def test_cart_apply_coupon(self):
+ with self.create_web_shop() as web_shop:
+ self.create_coupon()
+ cart = web_shop.POST_vsf_cart_create(None)
+
+ result = web_shop.POST_vsf_cart_apply_coupon(None, cart, 'CODE')
+
+ self.assertTrue(result)
+
+ @with_transaction(user=0)
+ def test_cart_delete_coupon(self):
+ with self.create_web_shop() as web_shop:
+ self.create_coupon()
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_apply_coupon(None, cart, 'CODE')
+
+ result = web_shop.POST_vsf_cart_delete_coupon(None, cart)
+
+ self.assertTrue(result)
+
+ @with_transaction(user=0)
+ def test_cart_coupon(self):
+ with self.create_web_shop() as web_shop:
+ self.create_coupon()
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_apply_coupon(None, cart, 'CODE')
+
+ result = web_shop.POST_vsf_cart_coupon(None, cart)
+
+ self.assertEqual(result, 'CODE')
+
+ @with_transaction(user=0)
+ def test_cart_coupon_empty(self):
+ with self.create_web_shop() as web_shop:
+ cart = web_shop.POST_vsf_cart_create(None)
+
+ result = web_shop.POST_vsf_cart_coupon(None, cart)
+
+ self.assertEqual(result, '')
+
+ @with_transaction(user=0)
+ def test_cart_totals(self):
+ with self.create_web_shop() as web_shop:
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ totals = web_shop.GET_vsf_cart_totals(None, cart)
+
+ self.assertEqual(totals, {
+ 'grand_total': 100,
+ 'items': [{
+ 'item_id': ANY,
+ 'sku': 'CODE',
+ 'qty': 1,
+ 'name': 'Product',
+ 'price': 100,
+ 'product_type': 'simple',
+ 'quote_id': ANY,
+ 'product_option': {
+ 'extension_attributes': {
+ },
+ },
+ }],
+ 'total_segments': [{
+ 'code': 'subtotal',
+ 'title': ANY,
+ 'value': 100,
+ }, {
+ 'code': 'tax',
+ 'title': ANY,
+ 'value': 0,
+ }, {
+ 'code': 'grand_total',
+ 'title': ANY,
+ 'value': 100,
+ }],
+ })
+
+ @with_transaction(user=0)
+ def test_cart_payment_methods(self):
+ with self.create_web_shop() as web_shop:
+ cart = web_shop.POST_vsf_cart_create(None)
+
+ result = web_shop.GET_vsf_cart_payment_methods(None, cart)
+
+ self.assertEqual(result, [])
+
+ @with_transaction(user=0)
+ def test_cart_shipping_methods(self):
+ pool = Pool()
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ carrier = self.create_carrier()
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ methods = web_shop.POST_vsf_cart_shipping_methods(
+ {'address': {'country_id': 'US'}}, cart)
+
+ self.assertEqual(methods, [{
+ 'carrier_code': str(carrier.id),
+ 'method_code': str(carrier.id),
+ 'carrier_title': "Carrier",
+ 'method_title': "Delivery",
+ 'price_incl_tax': Decimal(10),
+ }])
+
+ @with_transaction(user=0)
+ def test_cart_shipping_information(self):
+ pool = Pool()
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ carrier = self.create_carrier()
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+
+ information = web_shop.POST_vsf_cart_shipping_information({
+ 'addressInformation': {
+ 'shipping_address': {
+ 'country_id': 'US',
+ },
+ 'shipping_method_code': str(carrier.id),
+ 'shipping_carrier_code': str(carrier.id),
+ },
+ }, cart)
+
+ self.assertEqual(information, {
+ 'grand_total': 110,
+ 'items': ANY,
+ 'total_segments': [{
+ 'code': 'subtotal',
+ 'title': ANY,
+ 'value': 100,
+ }, {
+ 'code': 'shipping',
+ 'title': ANY,
+ 'value': 10,
+ }, {
+ 'code': 'tax',
+ 'title': ANY,
+ 'value': 0,
+ }, {
+ 'code': 'grand_total',
+ 'title': ANY,
+ 'value': 110,
+ }],
+ })
+
+ @with_transaction(user=0)
+ def test_order_create(self):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ carrier = self.create_carrier()
+ self.create_product(web_shop)
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+ cart = web_shop.POST_vsf_cart_create(None, token=token)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart, token=token)
+
+ result = web_shop.POST_vsf_order_create({
+ 'products': [{
+ 'sku': 'CODE',
+ 'qty': 1,
+ }],
+ 'addressInformation': {
+ 'shippingAddress': ADDRESS,
+ 'billingAddress': ADDRESS_COMPANY,
+ 'shipping_method_code': str(carrier.id),
+ 'shipping_carrier_code': str(carrier.id),
+ 'payment_method_code': 'cashondelivery',
+ 'payment_method_additional': {},
+ },
+ }, cart, token=token)
+
+ self.assertEqual(result, 'OK')
+
+ sale, = Sale.search([])
+ self.assertEqual(sale.state, 'confirmed')
+ self.assertEqual(len(sale.lines), 2)
+ self.assertEqual(sale.total_amount, Decimal(110))
+
+ @with_transaction(user=0)
+ def test_order_create_from_guest(self):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ Country = pool.get('country.country')
+ Country(name="US", code="US").save()
+ with self.create_web_shop() as web_shop:
+ carrier = self.create_carrier()
+ self.create_product(web_shop)
+ cart = web_shop.POST_vsf_cart_create(None)
+ web_shop.POST_vsf_cart_update({
+ 'cartItem': {
+ 'sku': 'CODE',
+ 'qty': 1,
+ },
+ }, cart)
+ web_shop.POST_vsf_user_create({
+ "customer": CUSTOMER,
+ "password": "TopSecretPassword",
+ })
+ token = web_shop.POST_vsf_user_login({
+ "username": CUSTOMER['email'],
+ "password": "TopSecretPassword",
+ })
+ result = web_shop.POST_vsf_order_create({
+ 'products': [{
+ 'sku': 'CODE',
+ 'qty': 1,
+ }],
+ 'addressInformation': {
+ 'shippingAddress': ADDRESS,
+ 'billingAddress': ADDRESS_COMPANY,
+ 'shipping_method_code': str(carrier.id),
+ 'shipping_carrier_code': str(carrier.id),
+ 'payment_method_code': 'cashondelivery',
+ 'payment_method_additional': {},
+ },
+ }, cart, token=token)
+
+ self.assertEqual(result, 'OK')
+
+ sale, = Sale.search([])
+ self.assertNotEqual(sale.party, web_shop.guest_party)
+ self.assertEqual(sale.state, 'confirmed')
+ self.assertEqual(len(sale.lines), 2)
+ self.assertEqual(sale.total_amount, Decimal(110))
+
+
+def suite():
+ suite = test_suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ WebVueStorefrontTestCase))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_web_shop_vue_storefront.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE,
+ checker=doctest_checker))
+ return suite
diff --git a/modules/web_shop_vue_storefront/tests/tools.py b/modules/web_shop_vue_storefront/tests/tools.py
new file mode 100644
index 0000000000..adadbefb5c
--- /dev/null
+++ b/modules/web_shop_vue_storefront/tests/tools.py
@@ -0,0 +1,10 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+
+class AnyDictWith(dict):
+ def __eq__(self, other):
+ if not isinstance(other, dict):
+ return False
+ items = list(sorted(filter(lambda i: i[0] in self, other.items())))
+ return list(sorted(self.items())) == items
diff --git a/modules/web_shop_vue_storefront/tox.ini b/modules/web_shop_vue_storefront/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/web_shop_vue_storefront/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/web_shop_vue_storefront/tryton.cfg b/modules/web_shop_vue_storefront/tryton.cfg
new file mode 100644
index 0000000000..1df97cb713
--- /dev/null
+++ b/modules/web_shop_vue_storefront/tryton.cfg
@@ -0,0 +1,17 @@
+[tryton]
+version=6.0.6
+depends:
+ ir
+ party
+ product
+ sale
+ web_shop
+ web_user
+extras_depend:
+ carrier
+ product_attribute
+ sale_promotion_coupon
+ sale_shipment_cost
+xml:
+ web.xml
+ message.xml
diff --git a/modules/web_shop_vue_storefront/view/shop_form.xml b/modules/web_shop_vue_storefront/view/shop_form.xml
new file mode 100644
index 0000000000..614eba41c3
--- /dev/null
+++ b/modules/web_shop_vue_storefront/view/shop_form.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop_vue_storefront/web.py b/modules/web_shop_vue_storefront/web.py
new file mode 100644
index 0000000000..af65b44172
--- /dev/null
+++ b/modules/web_shop_vue_storefront/web.py
@@ -0,0 +1,665 @@
+# 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 functools import wraps
+
+import elasticsearch
+from elasticsearch import Elasticsearch, VERSION as ES_VERSION
+
+from trytond.exceptions import RateLimitException
+from trytond.i18n import lazy_gettext, gettext
+from trytond.model import ModelSQL, Unique, fields
+from trytond.pool import PoolMeta, Pool
+from trytond.pyson import Eval
+
+from .exceptions import LoginException, NotFound, BadRequest
+
+
+def migrate_doc_type(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if ES_VERSION >= (7,):
+ kwargs = kwargs.copy()
+ doc_type = kwargs.pop('doc_type')
+ kwargs['index'] += '_' + doc_type
+ return func(*args, **kwargs)
+ return wrapper
+
+
+class VSFElasticsearch(Elasticsearch):
+
+ @migrate_doc_type
+ def index(self, **kwargs):
+ return super().index(**kwargs)
+
+ @migrate_doc_type
+ def delete(self, **kwargs):
+ return super().delete(**kwargs)
+
+
+def join_name(firstname, lastname):
+ # Use unbreakable spaces in firstname
+ # to prevent to split on them
+ firstname = firstname.replace(' ', ' ')
+ return ' '.join([firstname, lastname])
+
+
+def split_name(name):
+ return (name.split(' ', 1) + [''])[:2]
+
+
+def issubdict(dct, other):
+ for key, value in dct.items():
+ if value != other.get(key):
+ return False
+ return True
+
+
+def with_user(required=False):
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ pool = Pool()
+ User = pool.get('web.user')
+ token = kwargs.pop('token', None)
+ if token:
+ user = User.get_user(token)
+ else:
+ user = kwargs.get('user')
+ if required and not user:
+ raise LoginException()
+ kwargs['user'] = user
+ return func(*args, **kwargs)
+ return wrapper
+ return decorator
+
+
+class Shop(metaclass=PoolMeta):
+ __name__ = 'web.shop'
+
+ vsf_elasticsearch_url = fields.Char(
+ "Elasticsearch URL",
+ states={
+ 'required': Eval('type') == 'vsf',
+ 'invisible': Eval('type') != 'vsf',
+ },
+ depends=['type'])
+ vsf_elasticsearch_index = fields.Char(
+ "Elasticsearch Index",
+ states={
+ 'required': Eval('type') == 'vsf',
+ 'invisible': Eval('type') != 'vsf',
+ },
+ depends=['type'])
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.type.selection.append(('vsf', "Vue Storefront"))
+
+ @classmethod
+ def default_vsf_elasticsearch_url(cls):
+ return 'http://localhost:9200/'
+
+ @classmethod
+ def default_vsf_elasticsearch_index(cls):
+ return 'vue_storefront_catalog'
+
+ @classmethod
+ def view_attributes(cls):
+ return super().view_attributes() + [
+ ('//page[@id="vsf"]', 'states', {
+ 'invisible': Eval('type') != 'vsf',
+ }),
+ ]
+
+ @property
+ def to_sync(self):
+ result = super().to_sync
+ if self.type == 'vsf':
+ result = True
+ return result
+
+ def get_vsf_elasticsearch(self):
+ return VSFElasticsearch(self.vsf_elasticsearch_url)
+
+ @classmethod
+ def vsf_update(cls, shops=None):
+ pool = Pool()
+ Product = pool.get('product.product')
+ ProductTemplate = pool.get('product.template')
+ Category = pool.get('product.category')
+ try:
+ ProductAttribute = pool.get('product.attribute')
+ except KeyError:
+ ProductAttribute = None
+ if shops is None:
+ shops = cls.search([
+ ('type', '=', 'vsf'),
+ ])
+ cls.lock(shops)
+ for shop in shops:
+ es = shop.get_vsf_elasticsearch()
+ if ProductAttribute:
+ attributes = shop.get_attributes()
+ ProductAttribute.set_vsf_identifier(attributes)
+ categories = shop.get_categories()
+ Category.set_vsf_identifier(categories)
+ products, prices, taxes = shop.get_products()
+ Product.set_vsf_identifier(products)
+
+ templates = set()
+ for product in products:
+ if product.vsf_is_configurable(shop):
+ templates.add(product.template)
+ continue
+ entity = product.get_vsf_entity(
+ shop, price=prices[product.id], tax=taxes[product.id])
+ es.index(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='product',
+ id=product.vsf_identifier.id,
+ body=entity)
+ templates = ProductTemplate.browse(templates)
+ ProductTemplate.set_vsf_identifier(templates)
+ for template in templates:
+ template_products = template.get_vsf_products(shop)
+ price, tax = min(
+ (prices[p.id], taxes[p.id]) for p in template_products)
+ entity = template.get_vsf_entity(shop, price=price, tax=tax)
+ entity['configurable_children'] = [
+ product.get_vsf_entity(
+ shop, price=prices[product.id], tax=taxes[product.id])
+ for product in template_products]
+ es.index(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='product',
+ id=template.vsf_identifier.id,
+ body=entity)
+
+ for category in categories:
+ entity = category.get_vsf_entity(shop)
+ es.index(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='category',
+ id=category.vsf_identifier.id,
+ body=entity)
+
+ if ProductAttribute:
+ for attribute in attributes:
+ entity = attribute.get_vsf_entity(shop)
+ es.index(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='attribute',
+ id=attribute.vsf_identifier.id,
+ body=entity)
+
+ for product in shop.products_removed:
+ template = product.template
+ if template.vsf_identifier:
+ template_products = product.template.get_vsf_products(shop)
+ if not template_products:
+ try:
+ es.delete(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='product',
+ id=template.vsf_identifier.id)
+ except elasticsearch.exceptions.NotFoundError:
+ pass
+ if product.vsf_identifier:
+ try:
+ es.delete(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='product',
+ id=product.vsf_identifier.id)
+ except elasticsearch.exceptions.NotFoundError:
+ pass
+ shop.products_removed = []
+
+ for category in shop.categories_removed:
+ if category.vsf_identifier:
+ try:
+ es.delete(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='category',
+ id=category.vsf_identifier.id)
+ except elasticsearch.exceptions.NotFoundError:
+ pass
+ shop.categories_removed = []
+
+ if ProductAttribute:
+ for attribute in shop.attributes_removed:
+ if attribute.vsf_identifier:
+ try:
+ es.delete(
+ index=shop.vsf_elasticsearch_index,
+ doc_type='attribute',
+ id=attribute.vsf_identifier.id)
+ except elasticsearch.exceptions.NotFoundError:
+ pass
+ shop.attributes_removed = []
+
+ cls.save(shops)
+
+ def POST_vsf_user_create(self, data):
+ pool = Pool()
+ User = pool.get('web.user')
+ Party = pool.get('party.party')
+ firstname = data['customer']['firstname']
+ lastname = data['customer']['lastname']
+ email = data['customer']['email']
+ user = User(email=email, password=data['password'])
+ party = Party(name=join_name(firstname, lastname))
+ user.party = party
+ user.save()
+ firstname, lastname = split_name(user.party.name)
+ return {
+ 'email': user.email,
+ 'firstname': firstname,
+ 'lastname': lastname,
+ 'addresses': [],
+ }
+
+ def POST_vsf_user_login(self, data):
+ pool = Pool()
+ User = pool.get('web.user')
+ try:
+ user = User.authenticate(data['username'], data['password'])
+ except RateLimitException:
+ raise LoginException(gettext(
+ 'web_shop_vue_storefront.msg_login_wrong'))
+ if user:
+ return user.new_session()
+ else:
+ raise LoginException(gettext(
+ 'web_shop_vue_storefront.msg_login_wrong'))
+
+ def POST_vsf_user_reset_password(self, data):
+ pool = Pool()
+ User = pool.get('web.user')
+ users = User.search([
+ ('email', '=', data['email']),
+ ])
+ User.reset_password(users)
+
+ @with_user(required=True)
+ def POST_vsf_user_change_password(self, data, user):
+ pool = Pool()
+ User = pool.get('web.user')
+ try:
+ user = User.authenticate(user.email, data['currentPassword'])
+ except RateLimitException:
+ raise LoginException(gettext(
+ 'web_shop_vue_storefront.msg_login_wrong'))
+ if user:
+ user.password = data['newPassword']
+ user.save()
+ else:
+ raise LoginException(gettext(
+ 'web_shop_vue_storefront.msg_login_wrong'))
+
+ @with_user(required=True)
+ def GET_vsf_user_order_history(
+ self, data, user, pageSize='20', currentPage='1'):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ try:
+ pageSize = int(pageSize)
+ currentPage = int(currentPage)
+ except ValueError:
+ raise BadRequest()
+ sales = Sale.search([
+ ('party', '=', user.party),
+ ('state', 'in', ['confirmed', 'processing', 'done']),
+ ],
+ offset=pageSize * (currentPage - 1),
+ limit=pageSize,
+ order=[
+ ('sale_date', 'DESC'),
+ ('id', 'DESC'),
+ ])
+ items = []
+ for sale in sales:
+ items.append(sale.get_vsf_user_order_history())
+ return {'items': items}
+
+ @with_user(required=True)
+ def GET_vsf_user_me(self, data, user):
+ return user.get_vsf()
+
+ @with_user(required=True)
+ def POST_vsf_user_me(self, data, user):
+ user.set_vsf(data['customer'])
+ user.save()
+ return user.get_vsf()
+
+ def GET_vsf_stock_check(self, data, sku):
+ try:
+ return self.GET_vsf_stock_list(data, sku)[0]
+ except IndexError:
+ raise NotFound()
+
+ def GET_vsf_stock_list(self, data, skus):
+ pool = Pool()
+ Product = pool.get('product.product')
+ Template = pool.get('product.template')
+ skus = skus.split(',')
+ products = Product.search([
+ ('vsf_sku', 'in', skus),
+ ('web_shops', '=', self.id),
+ ])
+ products += Template.search([
+ ('vsf_sku', 'in', skus),
+ ('products.web_shops', '=', self.id),
+ ])
+ return [p.get_vsf_stock() for p in products]
+
+ @with_user()
+ def POST_vsf_cart_create(self, data, user=None):
+ party = user.party if user else None
+ sale = self.get_sale(party)
+ sale.save()
+ return sale.vsf_id
+
+ @with_user()
+ def GET_vsf_cart_pull(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ return [line.get_vsf() for line in sale.lines if line.product]
+
+ @with_user()
+ def POST_vsf_cart_update(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ SaleLine = pool.get('sale.line')
+ sale = Sale.search_vsf(cartId, self, user)
+ if data['cartItem'].get('item_id'):
+ line = SaleLine(data['cartItem']['item_id'])
+ if line.sale != sale:
+ raise BadRequest()
+ else:
+ line = SaleLine(sale=sale)
+ line.set_vsf(data['cartItem'])
+ line.save()
+ return line.get_vsf()
+
+ @with_user()
+ def POST_vsf_cart_delete(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ SaleLine = pool.get('sale.line')
+ sale = Sale.search_vsf(cartId, self, user)
+ line = SaleLine(data['cartItem']['item_id'])
+ if line.sale != sale:
+ raise BadRequest()
+ SaleLine.delete([line])
+ return True
+
+ @with_user()
+ def GET_vsf_cart_totals(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ return sale.get_vsf()
+
+ @with_user()
+ def GET_vsf_cart_payment_methods(self, data, cartId, user=None):
+ return []
+
+ @with_user()
+ def POST_vsf_cart_shipping_methods(self, data, cartId, user=None):
+ return []
+
+ @with_user()
+ def POST_vsf_cart_shipping_information(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ return sale.get_vsf()
+
+ @with_user(required=True)
+ def POST_vsf_order_create(self, data, cartId, user):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ self.vsf_order_create(data, sale, user)
+ return 'OK'
+
+ def vsf_order_create(self, data, sale, user):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ SaleLine = pool.get('sale.line')
+ sale.set_vsf(data, user)
+ sku2lines = {
+ line.product.vsf_sku: line for line in sale.lines if line.product}
+ for product in data.get('products', []):
+ sku = product['sku']
+ line = sku2lines.get(sku)
+ if not line:
+ line = SaleLine(sale=sale)
+ sku2lines[sku] = line
+ line.set_vsf(product)
+ sale.lines = sku2lines.values()
+ sale.save()
+ Sale.quote([sale])
+ payment_method = data['addressInformation']['payment_method_code']
+ if payment_method == 'cashondelivery':
+ Sale.confirm([sale])
+ return sale
+
+
+class ShopCoupon(metaclass=PoolMeta):
+ __name__ = 'web.shop'
+
+ @with_user()
+ def POST_vsf_cart_apply_coupon(self, data, cartId, coupon, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ PromotionCouponNumber = pool.get('sale.promotion.coupon.number')
+ try:
+ coupon, = PromotionCouponNumber.search([
+ ('number', 'ilike', coupon),
+ ], limit=1)
+ except ValueError:
+ return False
+ sale.coupons = [coupon]
+ sale.save()
+ return True
+
+ @with_user()
+ def POST_vsf_cart_delete_coupon(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ sale.coupons = []
+ sale.save()
+ return True
+
+ @with_user()
+ def POST_vsf_cart_coupon(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ if sale.coupons:
+ return sale.coupons[0].number
+ else:
+ return ''
+
+
+class ShopShipmentCost(metaclass=PoolMeta):
+ __name__ = 'web.shop'
+
+ @with_user()
+ def POST_vsf_cart_shipping_methods(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ methods = super().POST_vsf_cart_shipping_methods(
+ data, cartId, user=user)
+ sale = Sale.search_vsf(cartId, self, user)
+ sale.set_vsf_shipping_methods(data)
+ for carrier in sale.available_carriers:
+ method = carrier.get_vsf()
+ sale.carrier = carrier
+ method['price_incl_tax'] = sale.compute_shipment_cost()
+ methods.append(method)
+ return methods
+
+ @with_user()
+ def POST_vsf_cart_shipping_information(self, data, cartId, user=None):
+ pool = Pool()
+ Sale = pool.get('sale.sale')
+ sale = Sale.search_vsf(cartId, self, user)
+ sale.set_vsf(data, user)
+ sale.save()
+ return super().POST_vsf_cart_shipping_information(
+ data, cartId, user=user)
+
+
+class ShopVSFIdentifier(ModelSQL):
+ "Web Shop Vue Storefront Identifier"
+ __name__ = 'web.shop.vsf_identifier'
+
+ record = fields.Reference("Record", 'get_records', required=True)
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ t = cls.__table__()
+ cls._sql_constraints = [
+ ('record_unique', Unique(t, t.record),
+ 'web_shop_vue_storefront.msg_identifier_record_unique'),
+ ]
+
+ @classmethod
+ def get_records(cls):
+ pool = Pool()
+ Model = pool.get('ir.model')
+ get_name = Model.get_name
+ models = (klass.__name__ for _, klass in pool.iterobject()
+ if issubclass(klass, ShopVSFIdentifierMixin))
+ return [(m, get_name(m)) for m in models]
+
+
+class ShopVSFIdentifierMixin:
+
+ vsf_identifier = fields.Many2One(
+ 'web.shop.vsf_identifier',
+ lazy_gettext('web_shop_vue_storefront.msg_vsf_identifier'),
+ ondelete='RESTRICT', readonly=True)
+
+ @classmethod
+ def set_vsf_identifier(cls, records):
+ pool = Pool()
+ Identifier = pool.get('web.shop.vsf_identifier')
+ for record in records:
+ if not record.vsf_identifier:
+ record.vsf_identifier = Identifier(record=record)
+ cls.save(records)
+
+
+class User(metaclass=PoolMeta):
+ __name__ = 'web.user'
+
+ def get_vsf(self):
+ if not self.party:
+ return {
+ 'email': self.email,
+ }
+ firstname, lastname = split_name(self.party.name)
+ data = {
+ 'email': self.email,
+ 'firstname': firstname,
+ 'lastname': lastname,
+ 'addresses': (
+ [a.get_vsf() for a in self.party.addresses]
+ + [a.get_vsf(self.party) for p in self.secondary_parties
+ for a in p.addresses if p != self.party]),
+ }
+
+ default_billing = self.invoice_address
+ if not default_billing:
+ default_billing = self.party.address_get('invoice')
+ if default_billing:
+ data['default_billing'] = default_billing.id
+
+ default_shipping = self.shipment_address
+ if not default_shipping:
+ default_shipping = self.party.address_get('delivery')
+ if default_shipping:
+ data['default_shipping'] = default_shipping.id
+
+ return data
+
+ def set_vsf(self, data):
+ pool = Pool()
+ Party = pool.get('party.party')
+ Address = pool.get('party.address')
+ self.email = data['email']
+ if not self.party:
+ self.party = Party()
+ self.party.name = join_name(data['firstname'], data['lastname'])
+
+ default_billing = None
+ default_shipping = None
+ addresses = []
+ for address_data in data['addresses']:
+ address = self.set_vsf_address(address_data, self.party)
+ addresses.append(address)
+ if ((address.id and address.id == data.get('default_billing'))
+ or address_data.get('default_billing')):
+ default_billing = address
+ if ((address.id and address.id == data.get('default_shipping'))
+ or address_data.get('default_shipping')):
+ default_shipping = address
+
+ if address_data.get('id'):
+ if address_data['id'] != address.id:
+ address = Address(address_data['id'])
+ if (address.party != self.party
+ and address.party not in self.secondary_parties):
+ raise BadRequest()
+ address.active = False
+ addresses.append(address)
+ Address.save(addresses)
+ self.invoice_address = default_billing
+ self.shipment_address = default_shipping
+
+ def set_vsf_address(self, address_data, party):
+ pool = Pool()
+ Party = pool.get('party.party')
+ Address = pool.get('party.address')
+ Identifier = pool.get('party.identifier')
+
+ addresses = self.party.addresses
+ for party in self.secondary_parties:
+ addresses += party.addresses
+
+ for address in addresses:
+ if issubdict(address.get_vsf(party), address_data):
+ return address
+
+ address = Address()
+ party = address.party = self.party
+ if address_data.get('company'):
+ for company_party in self.secondary_parties:
+ tax_code = (
+ company_party.tax_identifier.code
+ if company_party.tax_identifier else '')
+ if (company_party.name == address_data['company']
+ and (not address_data.get('vat_id')
+ or tax_code == address_data['vat_id'])):
+ break
+ else:
+ identifier = Identifier()
+ identifier.set_vsf_tax_identifier(
+ address_data['vat_id'])
+ company_party = Party(
+ name=address_data['company'],
+ identifiers=[identifier])
+ company_party.save()
+ self.secondary_parties += (company_party,)
+ self.save()
+ address.party = company_party
+ address.set_vsf(address_data, party)
+ return address
diff --git a/modules/web_shop_vue_storefront/web.xml b/modules/web_shop_vue_storefront/web.xml
new file mode 100644
index 0000000000..fd8f6375dd
--- /dev/null
+++ b/modules/web_shop_vue_storefront/web.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ web.shop
+
+ shop_form
+
+
+
+ web.shop|vsf_update
+
+ days
+
+
+
diff --git a/modules/web_shop_vue_storefront_stripe/.drone.yml b/modules/web_shop_vue_storefront_stripe/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/web_shop_vue_storefront_stripe/.flake8 b/modules/web_shop_vue_storefront_stripe/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/web_shop_vue_storefront_stripe/CHANGELOG b/modules/web_shop_vue_storefront_stripe/CHANGELOG
new file mode 100644
index 0000000000..89a9cc5500
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/CHANGELOG
@@ -0,0 +1,8 @@
+Version 6.0.1 - 2021-09-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Initial release
\ No newline at end of file
diff --git a/modules/web_shop_vue_storefront_stripe/COPYRIGHT b/modules/web_shop_vue_storefront_stripe/COPYRIGHT
new file mode 100644
index 0000000000..908ef1c9b5
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2020-2021 Cédric Krier
+Copyright (C) 2020-2021 B2CK
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/web_shop_vue_storefront_stripe/LICENSE b/modules/web_shop_vue_storefront_stripe/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/web_shop_vue_storefront_stripe/MANIFEST.in b/modules/web_shop_vue_storefront_stripe/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/web_shop_vue_storefront_stripe/README.rst b/modules/web_shop_vue_storefront_stripe/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/web_shop_vue_storefront_stripe/__init__.py b/modules/web_shop_vue_storefront_stripe/__init__.py
new file mode 100644
index 0000000000..b7afa767a2
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/__init__.py
@@ -0,0 +1,14 @@
+# 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
+
+__all__ = ['register']
+
+from . import web
+
+
+def register():
+ Pool.register(
+ web.Shop,
+ module='web_shop_vue_storefront_stripe', type_='model')
diff --git a/modules/web_shop_vue_storefront_stripe/doc/index.rst b/modules/web_shop_vue_storefront_stripe/doc/index.rst
new file mode 100644
index 0000000000..355c077896
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/doc/index.rst
@@ -0,0 +1,7 @@
+Web Shop Vue Storefront Stripe Module
+#####################################
+
+The web_shop_vue_storefront_stripe module provides support of `Stripe payment
+for Vue Storefront`_ integration.
+
+.. _`Stripe payment for Vue Storefront`: https://github.com/develodesign/vsf-payment-stripe
diff --git a/modules/web_shop_vue_storefront_stripe/locale/bg.po b/modules/web_shop_vue_storefront_stripe/locale/bg.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/bg.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/ca.po b/modules/web_shop_vue_storefront_stripe/locale/ca.po
new file mode 100644
index 0000000000..9b23d33b99
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/ca.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr "Diari Stripe"
diff --git a/modules/web_shop_vue_storefront_stripe/locale/cs.po b/modules/web_shop_vue_storefront_stripe/locale/cs.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/cs.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/de.po b/modules/web_shop_vue_storefront_stripe/locale/de.po
new file mode 100644
index 0000000000..44b232ef40
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/de.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr "Stripe Journal"
diff --git a/modules/web_shop_vue_storefront_stripe/locale/es.po b/modules/web_shop_vue_storefront_stripe/locale/es.po
new file mode 100644
index 0000000000..e787f4ed82
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/es.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr "Diario Stripe"
diff --git a/modules/web_shop_vue_storefront_stripe/locale/es_419.po b/modules/web_shop_vue_storefront_stripe/locale/es_419.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/es_419.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/et.po b/modules/web_shop_vue_storefront_stripe/locale/et.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/et.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/fa.po b/modules/web_shop_vue_storefront_stripe/locale/fa.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/fa.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/fi.po b/modules/web_shop_vue_storefront_stripe/locale/fi.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/fi.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/fr.po b/modules/web_shop_vue_storefront_stripe/locale/fr.po
new file mode 100644
index 0000000000..30aa87080a
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/fr.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr "Journal Stripe"
diff --git a/modules/web_shop_vue_storefront_stripe/locale/hu.po b/modules/web_shop_vue_storefront_stripe/locale/hu.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/hu.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/id.po b/modules/web_shop_vue_storefront_stripe/locale/id.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/id.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/it.po b/modules/web_shop_vue_storefront_stripe/locale/it.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/it.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/lo.po b/modules/web_shop_vue_storefront_stripe/locale/lo.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/lo.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/lt.po b/modules/web_shop_vue_storefront_stripe/locale/lt.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/lt.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/nl.po b/modules/web_shop_vue_storefront_stripe/locale/nl.po
new file mode 100644
index 0000000000..3c2f8c423e
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/nl.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr "Stripe journaal"
diff --git a/modules/web_shop_vue_storefront_stripe/locale/pl.po b/modules/web_shop_vue_storefront_stripe/locale/pl.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/pl.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/pt.po b/modules/web_shop_vue_storefront_stripe/locale/pt.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/pt.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/ro.po b/modules/web_shop_vue_storefront_stripe/locale/ro.po
new file mode 100644
index 0000000000..e430421d37
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/ro.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr "Jurnal Stripe"
diff --git a/modules/web_shop_vue_storefront_stripe/locale/ru.po b/modules/web_shop_vue_storefront_stripe/locale/ru.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/ru.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/sl.po b/modules/web_shop_vue_storefront_stripe/locale/sl.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/sl.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/tr.po b/modules/web_shop_vue_storefront_stripe/locale/tr.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/tr.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/locale/zh_CN.po b/modules/web_shop_vue_storefront_stripe/locale/zh_CN.po
new file mode 100644
index 0000000000..9f91060081
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/locale/zh_CN.po
@@ -0,0 +1,7 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shop,stripe_journal:"
+msgid "Stripe Journal"
+msgstr ""
diff --git a/modules/web_shop_vue_storefront_stripe/setup.py b/modules/web_shop_vue_storefront_stripe/setup.py
new file mode 100755
index 0000000000..1d5c89539f
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/setup.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname, slice=None):
+ content = io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+ if slice:
+ content = '\n'.join(content.splitlines()[slice])
+ return content
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_web_shop_vue_storefront_stripe'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = []
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to support Stripe payment with Vue Storefront',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": (
+ 'https://hg.tryton.org/modules/web_shop_vue_storefront_stripe'),
+ },
+ keywords='web vue storefront PWA ecommerce Stripe',
+ package_dir={'trytond.modules.web_shop_vue_storefront_stripe': '.'},
+ packages=(
+ ['trytond.modules.web_shop_vue_storefront_stripe']
+ + ['trytond.modules.web_shop_vue_storefront_stripe.%s' % p
+ for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.web_shop_vue_storefront_stripe': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.fodt',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ web_shop_vue_storefront_stripe = trytond.modules.web_shop_vue_storefront_stripe
+ """, # noqa: E501
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/web_shop_vue_storefront_stripe/tests/__init__.py b/modules/web_shop_vue_storefront_stripe/tests/__init__.py
new file mode 100644
index 0000000000..25350865e8
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.web_shop_vue_storefront_stripe.tests.test_web_shop_vue_storefront_stripe import suite # noqa: E501
+except ImportError:
+ from .test_web_shop_vue_storefront_stripe import suite
+
+__all__ = ['suite']
diff --git a/modules/web_shop_vue_storefront_stripe/tests/test_web_shop_vue_storefront_stripe.py b/modules/web_shop_vue_storefront_stripe/tests/test_web_shop_vue_storefront_stripe.py
new file mode 100644
index 0000000000..72b6c422d2
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/tests/test_web_shop_vue_storefront_stripe.py
@@ -0,0 +1,19 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import unittest
+
+from trytond.tests.test_tryton import ModuleTestCase
+from trytond.tests.test_tryton import suite as test_suite
+
+
+class WebShopVueStorefrontStripeTestCase(ModuleTestCase):
+ 'Test Web Shop Vue Storefront Stripe module'
+ module = 'web_shop_vue_storefront_stripe'
+
+
+def suite():
+ suite = test_suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ WebShopVueStorefrontStripeTestCase))
+ return suite
diff --git a/modules/web_shop_vue_storefront_stripe/tox.ini b/modules/web_shop_vue_storefront_stripe/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/web_shop_vue_storefront_stripe/tryton.cfg b/modules/web_shop_vue_storefront_stripe/tryton.cfg
new file mode 100644
index 0000000000..4a63cca34e
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/tryton.cfg
@@ -0,0 +1,10 @@
+[tryton]
+version=6.0.2
+depends:
+ account_payment_stripe
+ ir
+ sale_payment
+ web_shop
+ web_shop_vue_storefront
+xml:
+ web.xml
diff --git a/modules/web_shop_vue_storefront_stripe/view/shop_form.xml b/modules/web_shop_vue_storefront_stripe/view/shop_form.xml
new file mode 100644
index 0000000000..c92a484d44
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/view/shop_form.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/modules/web_shop_vue_storefront_stripe/web.py b/modules/web_shop_vue_storefront_stripe/web.py
new file mode 100644
index 0000000000..428cc807f0
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/web.py
@@ -0,0 +1,45 @@
+# 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.model import fields
+from trytond.pool import PoolMeta, Pool
+from trytond.pyson import Eval
+
+
+class Shop(metaclass=PoolMeta):
+ __name__ = 'web.shop'
+
+ stripe_journal = fields.Many2One(
+ 'account.payment.journal', "Stripe Journal",
+ domain=[
+ ('stripe_account', '!=', None),
+ ('currency', '=', Eval('currency', -1)),
+ ],
+ depends=['currency'])
+
+ def vsf_order_create(self, data, sale, user):
+ pool = Pool()
+ Payment = pool.get('account.payment')
+ PaymentGroup = pool.get('account.payment.group')
+ sale = super().vsf_order_create(data, sale, user)
+ payment_method = data['addressInformation']['payment_method_code']
+ if payment_method == 'stripe':
+ payment_method_additional = (
+ data['addressInformation']['payment_method_additional'])
+ payment = Payment()
+ payment.company = sale.company
+ payment.kind = 'receivable'
+ payment.party = sale.party
+ payment.origin = sale
+ payment.amount = sale.total_amount
+ payment.journal = self.stripe_journal
+ payment.stripe_token = payment_method_additional['id']
+ payment.stripe_chargeable = True
+ payment.save()
+ Payment.approve([payment])
+ group = PaymentGroup(
+ company=payment.company,
+ journal=payment.journal,
+ kind=payment.kind)
+ group.save()
+ Payment.process([payment], lambda: group)
+ return sale
diff --git a/modules/web_shop_vue_storefront_stripe/web.xml b/modules/web_shop_vue_storefront_stripe/web.xml
new file mode 100644
index 0000000000..ae8ea5d8c6
--- /dev/null
+++ b/modules/web_shop_vue_storefront_stripe/web.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ web.shop
+
+ shop_form
+
+
+
diff --git a/modules/web_shortener/.drone.yml b/modules/web_shortener/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/web_shortener/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/web_shortener/.flake8 b/modules/web_shortener/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/web_shortener/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/web_shortener/CHANGELOG b/modules/web_shortener/CHANGELOG
new file mode 100644
index 0000000000..c0861de23d
--- /dev/null
+++ b/modules/web_shortener/CHANGELOG
@@ -0,0 +1,18 @@
+Version 6.0.1 - 2022-04-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Initial release
diff --git a/modules/web_shortener/COPYRIGHT b/modules/web_shortener/COPYRIGHT
new file mode 100644
index 0000000000..c34bb45354
--- /dev/null
+++ b/modules/web_shortener/COPYRIGHT
@@ -0,0 +1,16 @@
+Copyright (C) 2018-2022 B2CK
+Copyright (C) 2019-2022 Cédric Krier
+Copyright (C) 2018-2019 Nicolas Êvrard
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/web_shortener/LICENSE b/modules/web_shortener/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/web_shortener/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/web_shortener/MANIFEST.in b/modules/web_shortener/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/web_shortener/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/web_shortener/README.rst b/modules/web_shortener/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/web_shortener/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/web_shortener/__init__.py b/modules/web_shortener/__init__.py
new file mode 100644
index 0000000000..58454d4b10
--- /dev/null
+++ b/modules/web_shortener/__init__.py
@@ -0,0 +1,20 @@
+# 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 web
+from . import routes
+
+__all__ = ['register', 'routes']
+
+
+def register():
+ Pool.register(
+ web.ShortenedURL,
+ web.URLAccess,
+ module='web_shortener', type_='model')
+ Pool.register(
+ module='web_shortener', type_='wizard')
+ Pool.register(
+ module='web_shortener', type_='report')
diff --git a/modules/web_shortener/doc/index.rst b/modules/web_shortener/doc/index.rst
new file mode 100644
index 0000000000..46e65c2325
--- /dev/null
+++ b/modules/web_shortener/doc/index.rst
@@ -0,0 +1,33 @@
+Web Shortener Module
+####################
+
+The web_shortener module allows URLs to be shortened. It counts the number of
+times the URL is accessed and optionally triggers action.
+
+The module defines a route `/s/`
+which will redirect the queries to the URL registered previously with
+`ShortenedURL.get_url`.
+
+Models that need to be callable from a shortened URL must define the method
+`shortened_url_execute`. This class method will be called from the underlying
+queue with the record and the keywords arguments transmitted when calling
+`get_url`.
+
+Shortened URL
+*************
+
+- Shortened URL: The shortened URL
+- Redirect URL: The URL the request is redirected to
+- Record: The record on which `method` will be executed
+- Method: The name of the method to call on `record`
+- Count: The number of times this shortened URL has been triggered
+
+Configuration
+*************
+
+The web_shortener modules uses the parameter from the section:
+
+- `[web]`:
+
+ - `shortener_base`: The base URL without path for shortened URL.
+ The default value is composed with the configuration `[web]` `hostname`.
diff --git a/modules/web_shortener/locale/bg.po b/modules/web_shortener/locale/bg.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/bg.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/ca.po b/modules/web_shortener/locale/ca.po
new file mode 100644
index 0000000000..a825b5ec0a
--- /dev/null
+++ b/modules/web_shortener/locale/ca.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "Nombre de clics"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Mètode"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Registre"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "URL de redirecció"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "URL escurçada"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "URLs escurçades"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "URLs escurçades"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "URL curta"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "Accés URL"
diff --git a/modules/web_shortener/locale/cs.po b/modules/web_shortener/locale/cs.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/cs.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/de.po b/modules/web_shortener/locale/de.po
new file mode 100644
index 0000000000..0aa4f514ef
--- /dev/null
+++ b/modules/web_shortener/locale/de.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "Anzahl Klicks"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Methode"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Datensatz"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "URL Umleitung"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "Gekürzte URL"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "Gekürzte URLs"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "Gekürzte URLs"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "Gekürzte URL"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "URL Zugriff"
diff --git a/modules/web_shortener/locale/es.po b/modules/web_shortener/locale/es.po
new file mode 100644
index 0000000000..908454bedd
--- /dev/null
+++ b/modules/web_shortener/locale/es.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "Número de clicks"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Método"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Registro"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "URL de redirección"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "URL acortada"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "URLs acortadas"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "URLs acortadas"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "URL corta"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "Acceso URL"
diff --git a/modules/web_shortener/locale/es_419.po b/modules/web_shortener/locale/es_419.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/es_419.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/et.po b/modules/web_shortener/locale/et.po
new file mode 100644
index 0000000000..f841f3330d
--- /dev/null
+++ b/modules/web_shortener/locale/et.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "Klikid"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Meetod"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Kirje"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "Suunatud URL"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "Lühendatud URL"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "Lühendatud URL"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "Lühendatud URL"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "Lühike URL"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "URL-i juurdepääs"
diff --git a/modules/web_shortener/locale/fa.po b/modules/web_shortener/locale/fa.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/fa.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/fi.po b/modules/web_shortener/locale/fi.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/fi.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/fr.po b/modules/web_shortener/locale/fr.po
new file mode 100644
index 0000000000..6c408d7113
--- /dev/null
+++ b/modules/web_shortener/locale/fr.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "Nombre de clics"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Méthode"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Enregistrement"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "URL de redirection"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "URL raccourcie"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "URLs raccourcies"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "URLs raccourcies"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "URL courte"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "Accès URL"
diff --git a/modules/web_shortener/locale/hu.po b/modules/web_shortener/locale/hu.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/hu.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/id.po b/modules/web_shortener/locale/id.po
new file mode 100644
index 0000000000..311f5c0226
--- /dev/null
+++ b/modules/web_shortener/locale/id.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Metode"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/it.po b/modules/web_shortener/locale/it.po
new file mode 100644
index 0000000000..41cc58eda3
--- /dev/null
+++ b/modules/web_shortener/locale/it.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "Conteggio dei click"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Metodo"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Record"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "URL di rendirizzamento"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "URL abbreviato"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "URL abbreviati"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "URL abbreviati"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "URL breve"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "Accesso URL"
diff --git a/modules/web_shortener/locale/lo.po b/modules/web_shortener/locale/lo.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/lo.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/lt.po b/modules/web_shortener/locale/lt.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/lt.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/nl.po b/modules/web_shortener/locale/nl.po
new file mode 100644
index 0000000000..eba22a9c7d
--- /dev/null
+++ b/modules/web_shortener/locale/nl.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "aantal klikken"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "Methode"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "Record"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "Omleidings-URL"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "Verkorte URL"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "URL"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "Verkorte URL"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "Verkorte URL"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "Korte URL"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "URL-toegang"
diff --git a/modules/web_shortener/locale/pl.po b/modules/web_shortener/locale/pl.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/pl.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/pt.po b/modules/web_shortener/locale/pt.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/pt.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/ro.po b/modules/web_shortener/locale/ro.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/ro.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/ru.po b/modules/web_shortener/locale/ru.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/ru.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/sl.po b/modules/web_shortener/locale/sl.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/sl.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/tr.po b/modules/web_shortener/locale/tr.po
new file mode 100644
index 0000000000..b9147eb729
--- /dev/null
+++ b/modules/web_shortener/locale/tr.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr ""
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr ""
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr ""
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr ""
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr ""
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr ""
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr ""
diff --git a/modules/web_shortener/locale/zh_CN.po b/modules/web_shortener/locale/zh_CN.po
new file mode 100644
index 0000000000..d0dcea3d6c
--- /dev/null
+++ b/modules/web_shortener/locale/zh_CN.po
@@ -0,0 +1,43 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.shortened_url,count:"
+msgid "Click Count"
+msgstr "点击计数"
+
+msgctxt "field:web.shortened_url,method:"
+msgid "Method"
+msgstr "方法"
+
+msgctxt "field:web.shortened_url,record:"
+msgid "Record"
+msgstr "记录"
+
+msgctxt "field:web.shortened_url,redirect_url:"
+msgid "Redirect URL"
+msgstr "重定向网址"
+
+msgctxt "field:web.shortened_url,shortened_url:"
+msgid "Shortened URL"
+msgstr "网址短名"
+
+msgctxt "field:web.shortened_url.access,url:"
+msgid "URL"
+msgstr "网址"
+
+msgctxt "model:ir.action,name:act_shortened_url"
+msgid "Shortened URLs"
+msgstr "网址短名"
+
+msgctxt "model:ir.ui.menu,name:menu_shortened_url_form"
+msgid "Shortened URLs"
+msgstr "网址短名"
+
+msgctxt "model:web.shortened_url,name:"
+msgid "Short URL"
+msgstr "简短网址"
+
+msgctxt "model:web.shortened_url.access,name:"
+msgid "URL Access"
+msgstr "URL访问"
diff --git a/modules/web_shortener/routes.py b/modules/web_shortener/routes.py
new file mode 100644
index 0000000000..d5dd9c5da2
--- /dev/null
+++ b/modules/web_shortener/routes.py
@@ -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.
+
+try:
+ from http import HTTPStatus
+except ImportError:
+ from http import client as HTTPStatus
+
+from werkzeug.utils import redirect
+from werkzeug.exceptions import abort
+
+from trytond.protocols.wrappers import with_pool, with_transaction
+from trytond.wsgi import app
+
+
+@app.route('/s/$')
+@with_pool
+@with_transaction(readonly=False)
+def shortened(request, pool, shortened):
+ ShortenedURL = pool.get('web.shortened_url')
+
+ try:
+ shortened_url = ShortenedURL.get(shortened)
+ except IndexError:
+ abort(HTTPStatus.NOT_FOUND)
+
+ return redirect(shortened_url.access(), code=301)
diff --git a/modules/web_shortener/setup.py b/modules/web_shortener/setup.py
new file mode 100755
index 0000000000..609534f6e2
--- /dev/null
+++ b/modules/web_shortener/setup.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_web_shortener'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = ['python-sql', 'werkzeug']
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = []
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to plug a URL to an action',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/web_shortener',
+ },
+ keywords='tryton url',
+ package_dir={'trytond.modules.web_shortener': '.'},
+ packages=(
+ ['trytond.modules.web_shortener']
+ + ['trytond.modules.web_shortener.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.web_shortener': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.fodt',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Chinese (Simplified)',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ web_shortener = trytond.modules.web_shortener
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/web_shortener/tests/__init__.py b/modules/web_shortener/tests/__init__.py
new file mode 100644
index 0000000000..8cb6410757
--- /dev/null
+++ b/modules/web_shortener/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.web_shortener.tests.test_web_shortener import suite
+except ImportError:
+ from .test_web_shortener import suite
+
+__all__ = ['suite']
diff --git a/modules/web_shortener/tests/test_web_shortener.py b/modules/web_shortener/tests/test_web_shortener.py
new file mode 100644
index 0000000000..35e5ffcef6
--- /dev/null
+++ b/modules/web_shortener/tests/test_web_shortener.py
@@ -0,0 +1,37 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import random
+import sys
+import unittest
+
+
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.tests.test_tryton import suite as test_suite
+from trytond.pool import Pool
+
+
+class WebShortenerTestCase(ModuleTestCase):
+ 'Test Web Shortener module'
+ module = 'web_shortener'
+
+ @with_transaction()
+ def test_shorten(self):
+ pool = Pool()
+ ShortenedURL = pool.get('web.shortened_url')
+ expand = ShortenedURL._expand
+ shorten = ShortenedURL._shorten
+
+ self.assertEqual(expand(shorten(0)), 0)
+ self.assertEqual(expand(shorten(sys.maxsize)), sys.maxsize)
+
+ for _ in range(10):
+ x = random.randint(0, sys.maxsize)
+ self.assertEqual(expand(shorten(x)), x)
+
+
+def suite():
+ suite = test_suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ WebShortenerTestCase))
+ return suite
diff --git a/modules/web_shortener/tox.ini b/modules/web_shortener/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/web_shortener/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/web_shortener/tryton.cfg b/modules/web_shortener/tryton.cfg
new file mode 100644
index 0000000000..d073ca8e5e
--- /dev/null
+++ b/modules/web_shortener/tryton.cfg
@@ -0,0 +1,6 @@
+[tryton]
+version=6.0.2
+depends:
+ ir
+xml:
+ web.xml
diff --git a/modules/web_shortener/view/shortened_url_form.xml b/modules/web_shortener/view/shortened_url_form.xml
new file mode 100644
index 0000000000..53d79ebc7f
--- /dev/null
+++ b/modules/web_shortener/view/shortened_url_form.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/modules/web_shortener/view/shortened_url_list.xml b/modules/web_shortener/view/shortened_url_list.xml
new file mode 100644
index 0000000000..1ea56ac540
--- /dev/null
+++ b/modules/web_shortener/view/shortened_url_list.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/modules/web_shortener/web.py b/modules/web_shortener/web.py
new file mode 100644
index 0000000000..33e7c374bb
--- /dev/null
+++ b/modules/web_shortener/web.py
@@ -0,0 +1,131 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import string
+import logging
+from urllib.parse import urljoin, quote
+
+from sql.aggregate import Count
+
+from trytond.config import config
+from trytond.model import fields, ModelSQL, ModelView
+from trytond.pool import Pool
+from trytond.tools import grouped_slice, reduce_ids
+from trytond.transaction import Transaction
+from trytond.url import http_host
+from trytond.wsgi import Base64Converter
+
+ALPHABET = string.digits + string.ascii_lowercase
+URL_BASE = config.get('web', 'shortener_base', default=http_host())
+logger = logging.getLogger(__name__)
+
+
+class ShortenedURL(ModelSQL, ModelView):
+ "Short URL"
+ __name__ = 'web.shortened_url'
+
+ shortened_url = fields.Function(
+ fields.Char("Shortened URL"), 'get_url')
+ redirect_url = fields.Char("Redirect URL", readonly=True, required=True)
+ record = fields.Reference("Record", selection='get_models', readonly=True)
+ method = fields.Selection('get_methods', "Method", readonly=True)
+ count = fields.Function(fields.Integer("Click Count"), 'get_count')
+
+ @classmethod
+ def get_url(cls, shortened_urls, name):
+ urls = {}
+
+ url_parts = {
+ 'database': Base64Converter(None).to_url(
+ Transaction().database.name),
+ }
+ for shortened in shortened_urls:
+ url_parts['short_id'] = cls._shorten(shortened.id)
+ urls[shortened.id] = urljoin(
+ URL_BASE, quote('/s/%(database)s$%(short_id)s' % url_parts))
+ return urls
+
+ @classmethod
+ def get_count(cls, shortened_urls, name):
+ pool = Pool()
+ URLAccess = pool.get('web.shortened_url.access')
+
+ access = URLAccess.__table__()
+ cursor = Transaction().connection.cursor()
+
+ counts = {s.id: 0 for s in shortened_urls}
+ for sub_ids in grouped_slice(shortened_urls):
+ cursor.execute(*access.select(
+ access.url, Count(access.id),
+ where=reduce_ids(access.url, sub_ids),
+ group_by=[access.url]))
+ counts.update(cursor)
+ return counts
+
+ @classmethod
+ def _get_models(cls):
+ return []
+
+ @classmethod
+ def get_models(cls):
+ pool = Pool()
+ Model = pool.get('ir.model')
+ get_name = Model.get_name
+ models = cls._get_models()
+ return [(None, '')] + [(m, get_name(m)) for m in models]
+
+ @classmethod
+ def _get_methods(cls):
+ return []
+
+ @fields.depends('record')
+ def get_methods(self):
+ def func(option):
+ if not self.record:
+ return True
+ else:
+ name = option[0].split('|')[0]
+ return self.record.__class__.__name__ == name
+ return [(None, '')] + list(filter(func,
+ ((m, m) for m in self._get_methods())))
+
+ @classmethod
+ def get(cls, shortened_id):
+ id = cls._expand(shortened_id)
+ return cls.search([('id', '=', id)], limit=1)[0]
+
+ def access(self, **values):
+ pool = Pool()
+ URLAccess = pool.get('web.shortened_url.access')
+
+ URLAccess(url=self, **values).save()
+ if self.record and self.method:
+ model, method = self.method.split('|')
+ if model == self.record.__class__.__name__:
+ method = getattr(self.record.__class__.__queue__, method)
+ method([self.record])
+ return self.redirect_url
+
+ @staticmethod
+ def _shorten(integer):
+ "Turns an integer into a string in a given alphabet"
+ key_part = []
+ while integer:
+ integer, remainder = divmod(integer, len(ALPHABET))
+ key_part.append(ALPHABET[remainder])
+ else:
+ if not key_part:
+ key_part.append('0')
+ return ''.join(reversed(key_part))
+
+ @staticmethod
+ def _expand(key):
+ "Turns a key from a given alphabet to the corresponding ID"
+ return int(key, len(ALPHABET))
+
+
+class URLAccess(ModelSQL):
+ "URL Access"
+ __name__ = 'web.shortened_url.access'
+
+ url = fields.Many2One('web.shortened_url', "URL", required=True)
diff --git a/modules/web_shortener/web.xml b/modules/web_shortener/web.xml
new file mode 100644
index 0000000000..873897216d
--- /dev/null
+++ b/modules/web_shortener/web.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ web.shortened_url
+ tree
+ shortened_url_list
+
+
+
+ web.shortened_url
+ form
+ shortened_url_form
+
+
+
+ Shortened URLs
+ web.shortened_url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/web_user/.drone.yml b/modules/web_user/.drone.yml
new file mode 100644
index 0000000000..2049d30a49
--- /dev/null
+++ b/modules/web_user/.drone.yml
@@ -0,0 +1,68 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - DB_CACHE=/cache
+ - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox
+ - tox -e "${TOXENV}-${DATABASE}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+services:
+ postgresql:
+ image: postgres
+ environment:
+ - POSTGRES_HOST_AUTH_METHOD=trust
+ command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
+ when:
+ matrix:
+ DATABASE: postgresql
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: sqlite
+ - IMAGE: python:3.6
+ TOXENV: py36
+ DATABASE: postgresql
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: sqlite
+ - IMAGE: python:3.7
+ TOXENV: py37
+ DATABASE: postgresql
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: sqlite
+ - IMAGE: python:3.8
+ TOXENV: py38
+ DATABASE: postgresql
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: sqlite
+ - IMAGE: python:3.9
+ TOXENV: py39
+ DATABASE: postgresql
diff --git a/modules/web_user/.flake8 b/modules/web_user/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/modules/web_user/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/modules/web_user/CHANGELOG b/modules/web_user/CHANGELOG
new file mode 100644
index 0000000000..8cdc081964
--- /dev/null
+++ b/modules/web_user/CHANGELOG
@@ -0,0 +1,38 @@
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+* Add secondary parties on user
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 2.7
+* Add remove and reset session methods
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+* Erase user email when party is erased
+* Require email only for active user
+* Add maximum authentication attempt
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+* Increase session randomness to 32 bytes
+* Manage email case insensitively
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Initial release
diff --git a/modules/web_user/COPYRIGHT b/modules/web_user/COPYRIGHT
new file mode 100644
index 0000000000..e0c9acfac7
--- /dev/null
+++ b/modules/web_user/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2016-2021 Cédric Krier
+Copyright (C) 2016-2021 B2CK
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/modules/web_user/LICENSE b/modules/web_user/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/modules/web_user/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/modules/web_user/MANIFEST.in b/modules/web_user/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/modules/web_user/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/modules/web_user/README.rst b/modules/web_user/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/modules/web_user/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/modules/web_user/__init__.py b/modules/web_user/__init__.py
new file mode 100644
index 0000000000..bfcfa271b8
--- /dev/null
+++ b/modules/web_user/__init__.py
@@ -0,0 +1,26 @@
+# 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 user
+from . import party
+from . import ir
+
+
+def register():
+ Pool.register(
+ user.User,
+ user.User_PartySecondary,
+ user.UserAuthenticateAttempt,
+ user.UserSession,
+ ir.Email,
+ ir.EmailTemplate,
+ module='web_user', type_='model')
+ Pool.register(
+ party.Replace,
+ party.Erase,
+ module='web_user', type_='wizard')
+ Pool.register(
+ user.EmailValidation,
+ user.EmailResetPassword,
+ module='web_user', type_='report')
diff --git a/modules/web_user/doc/index.rst b/modules/web_user/doc/index.rst
new file mode 100644
index 0000000000..3495e03652
--- /dev/null
+++ b/modules/web_user/doc/index.rst
@@ -0,0 +1,43 @@
+Web User Module
+###############
+
+The web_user module provides facilities to manage external user accessing from
+the web.
+
+User
+****
+
+A user is uniquely identified by an email and he is authenticated using a
+hashed password. The user can be linked to a Party.
+
+Two actions are available:
+
+- The *Validate E-mail* which sent an e-mail to the user with a link to an URL
+ that ensures the address exists.
+- The *Reset Password* which sent an e-mail to the user with a link to an URL
+ to set a new password.
+
+Configuration
+*************
+
+The web_user module uses parameters from different sections:
+
+- `web`:
+
+ - `reset_password_url`: the URL to reset the password to which the
+ parameters `email` and `token` will be added.
+
+ - `email_validation_url`: the URL for email validation to which the
+ parameter `token` will be added.
+
+- `email`:
+
+ - `from`: the origin address to send emails.
+
+- `session`:
+
+ - `web_timeout`: defines in seconds the validity of the web session.
+ Default: 30 days.
+
+ - `web_timeout_reset`: in seconds the validity of the reset password token.
+ Default: 1 day.
diff --git a/modules/web_user/email_reset_password.html b/modules/web_user/email_reset_password.html
new file mode 100644
index 0000000000..aa78c7bdb7
--- /dev/null
+++ b/modules/web_user/email_reset_password.html
@@ -0,0 +1,32 @@
+
+
+
+ Reset Password
+
+
+
+
+
+
Reset Password
+
We received a request to reset the password for your account,
+ ${user.email}
Button is not working? Paste this into your browser:
+
${user.get_email_validation_url()}
+
If you received this email by mistake, just ignore it.
+
+
+
+
+
diff --git a/modules/web_user/ir.py b/modules/web_user/ir.py
new file mode 100644
index 0000000000..90d86ce07f
--- /dev/null
+++ b/modules/web_user/ir.py
@@ -0,0 +1,55 @@
+# 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 PoolMeta, Pool
+from trytond.tools import escape_wildcard
+
+
+class Email(metaclass=PoolMeta):
+ __name__ = 'ir.email'
+
+ @classmethod
+ def _match(cls, name, email):
+ pool = Pool()
+ User = pool.get('web.user')
+ yield from super()._match(name, email)
+ domain = ['OR']
+ for field in ['party.name', 'email']:
+ for value in [name, email]:
+ if value and len(value) >= 3:
+ domain.append(
+ (field, 'ilike', '%' + escape_wildcard(value) + '%'))
+ for user in User.search([
+ ('email', '!=', ''),
+ domain,
+ ], order=[]):
+ yield user.party.name if user.party else '', user.email
+
+
+class EmailTemplate(metaclass=PoolMeta):
+ __name__ = 'ir.email.template'
+
+ @classmethod
+ def email_models(cls):
+ return super().email_models() + ['web.user']
+
+ @classmethod
+ def _get_address(cls, record):
+ pool = Pool()
+ User = pool.get('web.user')
+ address = super()._get_address(record)
+ if isinstance(record, User):
+ name = None
+ if record.party:
+ name = record.party.name
+ address = (name, record.email)
+ return address
+
+ @classmethod
+ def _get_language(cls, record):
+ pool = Pool()
+ User = pool.get('web.user')
+ language = super()._get_language(record)
+ if isinstance(record, User):
+ if record.party:
+ language = cls._get_language(record.party)
+ return language
diff --git a/modules/web_user/locale/bg.po b/modules/web_user/locale/bg.po
new file mode 100644
index 0000000000..6375ef11ce
--- /dev/null
+++ b/modules/web_user/locale/bg.po
@@ -0,0 +1,176 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Управление на партньор"
+
+#, fuzzy
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Парола"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Управление на партньор"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Потребител"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Потребителско име"
+
+#, fuzzy
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Ключ"
+
+#, fuzzy
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Потребител"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Проверка"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/ca.po b/modules/web_user/locale/ca.po
new file mode 100644
index 0000000000..bf4e39336f
--- /dev/null
+++ b/modules/web_user/locale/ca.po
@@ -0,0 +1,163 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "Correu electrònic"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "Token del correu electrònic"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "Correu electrònic vàlid"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Tercer"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Contrasenya"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Hash de la contrasenya"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Token restableix la contrasenya"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Expiració token restableix contrasenya"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr "Tercers secundaris"
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Tercer"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Usuari"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr "Galeta de dispositiu"
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "Adreça IP"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "Xarxa IP"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Nom usuari"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Clau"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Usuari"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Usuaris web"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Restableix la contrasenya"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Validació correu electrònic"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "El correu electrònic de l'usuari web ha de ser únic."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr "La clau de sessió de l'usuari web ha de ser única."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Restableix la contrasenya"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Valida correu electrònic"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Usuaris web"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Usuari web"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Usuari Web - Tercers Secundaris"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Intent d'autenticació usuari web"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Sessió usuari web"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "El botó no funciona? Empega això al teu navegador:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "Si no heu fet aquesta petició, podeu ignorar aquest correu."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Restableix la contrasenya"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "Hem rebut una petició per restablir la contrasenya del vostre compte,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "El botó no funciona? Empega això al teu navegador:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Validació correu electrònic"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Si heu rebut aquest correu per error, ignoreu-lo."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Només un pas més..."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Valida"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Restableix la contrasenya"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Valida correu electrònic"
diff --git a/modules/web_user/locale/cs.po b/modules/web_user/locale/cs.po
new file mode 100644
index 0000000000..11696b5f66
--- /dev/null
+++ b/modules/web_user/locale/cs.po
@@ -0,0 +1,169 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr ""
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr ""
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Web Users"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/de.po b/modules/web_user/locale/de.po
new file mode 100644
index 0000000000..9db596660e
--- /dev/null
+++ b/modules/web_user/locale/de.po
@@ -0,0 +1,173 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-Mail"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "E-Mail Token"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "E-Mail gültig"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Partei"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Passwort"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Passwort Hash"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Passwortrücksetzungstoken"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Passwortrücksetzungstoken Verfall"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr "Sekundäre Parteien"
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Partei"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Benutzer"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr "Geräte Cookie"
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "IP-Adresse"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "IP-Netz"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Benutzername"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Schlüssel"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Benutzer"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Webbenutzer"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Passwort zurücksetzen"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "E-Mail-Validierung"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+"Die E-Mail eines aktiven Webbenutzers kann nur einmal vergeben werden."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+"Der Sitzungsschlüssel eines Webbenutzers kann nur einmal vergeben werden."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Passwort zurücksetzen"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "E-Mail validieren"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Webbenutzer"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Webbenutzer"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Webbenutzer - Sekundäre Partei"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Webbenutzer Authentifizierungsversuch"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Webbenutzer Sitzung"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+"Wenn der Button nicht funktioniert, kopieren Sie bitte die folgende Zeile in"
+" die Adresszeile Ihres Browsers:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+"Ignorieren Sie bitte diese E-Mail, wenn Sie nicht der Urheber dieser Anfrage"
+" sind."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Passwort zurücksetzen"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+"Wir haben eine Anfrage zur Rücksetzung des Passworts für Ihr Konto erhalten,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+"Wenn der Button nicht funktioniert, kopieren Sie bitte die folgende Zeile in"
+" die Adresszeile Ihres Browsers:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "E-Mail-Validierung"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+"Ignorieren Sie bitte diese E-Mail, wenn Sie sie irrtümlich erhalten haben."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Nur noch ein Schritt..."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Prüfen"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Passwort zurücksetzen"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "E-Mail validieren"
diff --git a/modules/web_user/locale/es.po b/modules/web_user/locale/es.po
new file mode 100644
index 0000000000..3c39caad7d
--- /dev/null
+++ b/modules/web_user/locale/es.po
@@ -0,0 +1,166 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "Correo electrónico"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "Token del correo electrónico"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "Correo electrónico válido"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Tercero"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Contraseña"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Hash de la contraseña"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Restablecer el token de la contraseña"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Restablecer el token de expiración de la contraseña"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr "Terceros secundarios"
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Tercero"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Usuario"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr "Cookie del dispositivo"
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "Dirección IP"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "Red IP"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Nombre usuario"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Clave"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Usuario"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Usuarios web"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Restablecer contraseña"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Validación correo electrónico"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "El correo electrónico del usuario web debe ser único."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr "La clave de sesión del usuario web debe ser única."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Restablecer contraseña"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validar correo electrónico"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Usuarios web"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Usuario web"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Usuario web - Tercero secundario"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Intento de identificación usuario web"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Sesión usuario web"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "¿El botón no funciona? Pega esto en tu navegador:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+"Si no habéis realizado esta petición, podéis ignorar este correo "
+"electrónico."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Restablecer contraseña"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+"Hemos recibido una solicitud para reiniciar la contraseña de vuestra cuenta,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "¿El botón no funciona? Pega esto en tu navegador:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Validación correo electrónico"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Si ha recibido este correo electrónico por error, por favor ignórelo."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Solo un paso mas..."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Validar"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Restablecer contraseña"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validar correo electrónico"
diff --git a/modules/web_user/locale/es_419.po b/modules/web_user/locale/es_419.po
new file mode 100644
index 0000000000..152ff38c17
--- /dev/null
+++ b/modules/web_user/locale/es_419.po
@@ -0,0 +1,163 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr ""
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr ""
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr ""
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr ""
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr ""
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr ""
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr ""
diff --git a/modules/web_user/locale/et.po b/modules/web_user/locale/et.po
new file mode 100644
index 0000000000..a5d5864349
--- /dev/null
+++ b/modules/web_user/locale/et.po
@@ -0,0 +1,165 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-kiri"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Osapool"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Salasõna"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Salasõna räsi"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Osapool"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Kasutaja"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "IP-aadress"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "IP-võrk"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Logi sisse"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Võti"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Kasutaja"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Lähtesta salasõna"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Lähtesta salasõna"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr ""
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Nupp ei tööta? Aseta see link brauseri aadressreale:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "Kui sa ei teinud seda päringut, siis ignoreeri seda e-kirja."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Lähtesta salasõna"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "Saime teate salasõna lähtestamiseks,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Nupp ei tööta? Aseta see link brauseri aadressreale:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Lähtesta salasõna"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr ""
diff --git a/modules/web_user/locale/fa.po b/modules/web_user/locale/fa.po
new file mode 100644
index 0000000000..c58dc6af6f
--- /dev/null
+++ b/modules/web_user/locale/fa.po
@@ -0,0 +1,166 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "ایمیل"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "نشانه(Token) ایمیل"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "ایمیل معتبر است"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "نهاد/سازمان"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "کلمه عبور"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "کلمه عبور درهم"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "بازنشانی نشانه(Token) کلمه عبور"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "بازنشانی انقضای نشانه(Token) کلمه عبور"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "نهاد/سازمان"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "کاربر"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "آدرس آی پی"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "آی پی شبکه"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "ورود به سیستم"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "کلید"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "کاربر"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "کاربران وب"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "بازنشانی رمز عبور"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "اعتبار سنجی ایمیل"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "پست الکترونیکی کاربر فعال وب سایت باید منحصر به فرد باشد."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr "کلید جلسه کاربر وب باید منحصر به فرد باشد."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "کاربران وب"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "کاربر وب"
+
+#, fuzzy
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "جلسه کاری کاربر وب"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "تلاش برای هوّیت سنجی کاربر وب"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "جلسه کاری کاربر وب"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "دکمه کار نمی کند؟ این آدرس را در مرورگر خود مرور کنید:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "اگراین درخواست را نکرده باشید، می توانید این ایمیل را نادیده بگیرید."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "بازنشانی کلمه عبور"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "مایک درخواست برای بازنشانی کلمه عبور حساب کاربری شما دریافت کردیم،"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "دکمه کار نمی کند؟ این آدرس را در مرورگر خود مرور کنید:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "اعتبار سنجی ایمیل"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "اگر این ایمیل را به اشتباه دریافت کردید، فقط آن را نادیده بگیرید."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "فقط یک گام دیگه.."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "تایید اعتبار"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "بازنشانی کلمه عبور"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "ایمیل معتبر است"
diff --git a/modules/web_user/locale/fi.po b/modules/web_user/locale/fi.po
new file mode 100644
index 0000000000..11696b5f66
--- /dev/null
+++ b/modules/web_user/locale/fi.po
@@ -0,0 +1,169 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr ""
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr ""
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Web Users"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/fr.po b/modules/web_user/locale/fr.po
new file mode 100644
index 0000000000..a697087f79
--- /dev/null
+++ b/modules/web_user/locale/fr.po
@@ -0,0 +1,166 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-mail"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "Jeton d'E-mail"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "E-mail valide"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Tiers"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Mot de passe"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Hachage de mot de passe"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Jeton de réinitialisation du mot de passe"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Expiration du jeton de réinitialisation du mot de passe"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr "Tiers secondaires"
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Tiers"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Utilisateur"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr "Cookie de l'appareil"
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "Adresse IP"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "Réseau IP"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Identifiant"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Clé"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Utilisateur"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Utilisateurs web"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Réinitialiser le mot de passe"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "E-mail de validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "L’E-mail d'un utilisateur web actif doit être unique."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr "La session d'un utilisateur web doit être unique."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Réinitialiser le mot de passe"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Valider l'e-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Utilisateurs web"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Utilisateur web"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Utilisateur Web - Tiers secondaire"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Tentative d'authentification d'utilisateur web"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Session d'utilisateur web"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Le bouton ne fonctionne pas ? Copier ceci dans votre navigateur :"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+"Si vous n'avez pas fait cette demande, vous pouvez ignorer cet E-mail."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Réinitialiser le mot de passe"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+"Nous avons reçu une demande de réinitialisation du mot de passe de votre "
+"compte,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Le bouton ne fonctionne pas ? Copier ceci dans votre navigateur :"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Validation d'E-mail"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Si vous avez reçu cet E-mail par erreur, ignorez le."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Encore une étape.."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Valider"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Réinitialiser le mot de passe"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Valider l'E-mail"
diff --git a/modules/web_user/locale/hu.po b/modules/web_user/locale/hu.po
new file mode 100644
index 0000000000..2fe2fcf148
--- /dev/null
+++ b/modules/web_user/locale/hu.po
@@ -0,0 +1,165 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-mail"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "E-mail cím leellenőrizve"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Ügyfél"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Jelszó"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Jelszó hash"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr "Másodlagos ügyfelek"
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Ügyfél"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Felhasználó"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Bejelentkezés"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Kulcs"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Felhasználó"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Webáruház felhasználók"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Jelszó visszaállítása"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "E-mail cím ellenőrzés"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "Nem lehet több aktív webes felhasználónak ugyanaz az e-mail címe."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Jelszó visszaállítása"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "E-mail cím leellenőrzése"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Webáruház felhasználók"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Webáruház felhasználó"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "A gomb nem működik? Másolja be ezt a böngészőjébe:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+"Ha nem Ön küldte ezt a kérés, akkor nyugodtan hagyja figyelmen kívül ezt az "
+"e-mailt."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Jelszó visszaállítása"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "Kaptunk egy kérést, hogy állítsuk vissza a felhasználója jelszavát,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "A gomb nem működik? Másolja be ezt a böngészőjébe:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "E-mail cím ellenőrzés"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Ha tévedésből kapta ezt az e-mailt, hagyja nyugodtan figyelmen kívül."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Csak még egy lépés.."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Ellenőrzés"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Jelszó visszaállítása"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "E-mail cím leellenőrzése"
diff --git a/modules/web_user/locale/id.po b/modules/web_user/locale/id.po
new file mode 100644
index 0000000000..0b767b0b3c
--- /dev/null
+++ b/modules/web_user/locale/id.po
@@ -0,0 +1,165 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-mail"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Pihak"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Kata sandi"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Pihak"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Pengguna"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "Alamat IP"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Kunci"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Pengguna"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Pengguna Web"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Atur Ulang Kata Sandi"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Atur Ulang Kata Sandi"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr ""
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Tombol tidak berfungsi? Lekatkan ini pada browser Anda:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "Jika bukan Anda yang membuat permintaan ini, abaikan email ini."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Atur Ulang Kata Sandi"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "Kami menerima permintaan untuk mengatur ulang kata sandi akun Anda,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Tombol tidak berfungsi? Lekatkan ini pada browser Anda:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Validasi Email"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Jika Anda menerima email ini karena kesalahan, abaikan saja."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Tinggal satu langkah lagi..."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Atur Ulang Kata Sandi"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr ""
diff --git a/modules/web_user/locale/it.po b/modules/web_user/locale/it.po
new file mode 100644
index 0000000000..b96b01db6c
--- /dev/null
+++ b/modules/web_user/locale/it.po
@@ -0,0 +1,181 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-mail"
+
+#, fuzzy
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "_E-Mail..."
+
+#, fuzzy
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "_E-Mail..."
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Controparte"
+
+#, fuzzy
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Password"
+
+#, fuzzy
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Hash della Password"
+
+#, fuzzy
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Reset Password"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Controparte"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Utente"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "Indirizzo IP"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Login"
+
+#, fuzzy
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Chiave"
+
+#, fuzzy
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Utente"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Sessione"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+#, fuzzy
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Sessione"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Validate"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/lo.po b/modules/web_user/locale/lo.po
new file mode 100644
index 0000000000..61e8fcbe61
--- /dev/null
+++ b/modules/web_user/locale/lo.po
@@ -0,0 +1,173 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "ອີ-ເມວລ໌"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "ອີ-ເມວລ໌ ຢັງຢືນຕົວຕົນ"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "ອີ-ເມວລ໌ ຖືກຕ້ອງ"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "ພາກສ່ວນ"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "ລະຫັດຜ່ານ"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "ລະຫັດຜ່ານປະສົມ"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "ຕັ້ງລະຫັດຢັ້ງຢືນຜ່ານຄືນໃໝ່"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "ການຕັ້ງລະຫັດຢັ້ງຢືນຜ່ານຄືນໃໝ່ໝົດເວລາແລ້ວ"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "ພາກສ່ວນ"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "ຜູ້ໃຊ້"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "ເຊື່ອມຕໍ່ເຂົ້າລະບົບ"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "ຄຳສຳຄັນ"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "ຜູ້ໃຊ້"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "ຜູ້ໃຊ້ງານເວັບ"
+
+#, fuzzy
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "ເທື່ອເຂົ້າໃຊ້ຂອງຜູ້ໃຊ້ງານເວັບ"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "ພະຍາຍາມກວດສອບສິດທີ່ຖືກຕ້ອງຂອງຜູ້ໃຊ້ງານເວັບ"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "ເທື່ອເຂົ້າໃຊ້ຂອງຜູ້ໃຊ້ງານເວັບ"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "ປຸ່ມບໍ່ເຮັດວຽກບໍ? ໃຫ້ ວາງ ອັນນີ້ລົງໃສ່ ໂຕທ່ອງເວັບຂອງທ່ານ:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "ຖ້າຫາກທ່ານບໍ່ໄດ້ຮ້ອງຂໍ ອ-ເມວລ໌ ນີ້, ທ່ານບໍ່ຕ້ອງຮັບຮູ້ ອີ-ເມວລ໌ ນີ້."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "ຕັ້ງຄ່າລະຫັດຜ່ານຄືນໃໝ່"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "ພວກເຮົາໄດ້ຮັບຄຳຮ້ອງຂໍໃຫ້ຕັ້ງຄ່າລະຫັດຜ່ານບັນຊີໃຊ້ງານຂອງທ່ານຄືນໃໝ່,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "ປຸ່ມບໍ່ເຮັດວຽກບໍ? ໃຫ້ ວາງ ອັນນີ້ລົງໃສ່ ໂຕທ່ອງເວັບຂອງທ່ານ:"
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "ການຢືນຢັນຄວາມຖືກຕ້ອງ ອີ-ເມວລ໌"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+"ຖ້າຫາກທ່ານໄດ້ຮັບ ອີ-ເມວລ໌ ນີ້ ໂດຍຄວາມຜິດພາດໃດໜຶ່ງ, ພຽງແຕ່ໃຫ້ທ່ານ ບໍ່ຮັບຮູ້ "
+"ອີ-ເມວລ໌ ນີ້ກໍພໍ."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "ພຽງແຕ່ອີກບາດກ້າວໜຶ່ງທໍ່ນັ້ນ.."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "ກວດສອບຄວາມຖືກຕ້ອງ"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "ຕັ້ງຄ່າລະຫັດຜ່ານຄືນໃໝ່"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "ກວດສອບຄວາມຖືກຕ້ອງ ອີ-ເມວລ໌"
diff --git a/modules/web_user/locale/lt.po b/modules/web_user/locale/lt.po
new file mode 100644
index 0000000000..e69d7008aa
--- /dev/null
+++ b/modules/web_user/locale/lt.po
@@ -0,0 +1,170 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Kontrahentas"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr ""
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Kontrahentas"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Naudotojas"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "IP adresas"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Naudotojas"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Atstatyti slaptažodį"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Atstatyti slaptažodį"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/nl.po b/modules/web_user/locale/nl.po
new file mode 100644
index 0000000000..04fbe82224
--- /dev/null
+++ b/modules/web_user/locale/nl.po
@@ -0,0 +1,166 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-mail"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "E-mail token"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "E-mail geldig"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Relatie"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Paswoord"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Wachtwoord Hash"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Reset wachtwoord token"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Reset wachtwoord token verlopen"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr "Secundaire Relaties"
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Relatie"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Gebruiker"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "IP Adres"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "IP-netwerk"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Inlognaam"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Sleutel"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Gebruiker"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Webgebruikers"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "E-mailvalidatie"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "E-mail van actieve webgebruiker moet uniek zijn."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr "Webgebruikerssessiesleutel moet uniek zijn."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Email valideren"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Webgebruikers"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web gebruiker"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Webgebruiker -Secundaire Relatie"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Poging tot authenticatie van webgebruikers"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Webgebruikerssessie"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Knop werkt niet? Plak dit in uw browser:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "Als je dit verzoek niet hebt gedaan, kun je deze e-mail negeren."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset wachtwoord"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+"We hebben een verzoek ontvangen om het wachtwoord voor uw account opnieuw in"
+" te stellen,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Knop werkt niet? Plak dit in uw browser:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "E-mailvalidatie"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+"Als je deze e-mail per ongeluk hebt ontvangen, negeer deze dan gewoon."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Nog maar een stap.."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Goedkeuren"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset wachtwoord"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Email valideren"
diff --git a/modules/web_user/locale/pl.po b/modules/web_user/locale/pl.po
new file mode 100644
index 0000000000..11696b5f66
--- /dev/null
+++ b/modules/web_user/locale/pl.po
@@ -0,0 +1,169 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr ""
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr ""
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Web Users"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/pt.po b/modules/web_user/locale/pt.po
new file mode 100644
index 0000000000..570e38fe40
--- /dev/null
+++ b/modules/web_user/locale/pt.po
@@ -0,0 +1,170 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "E-mail"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "Token do E-mail"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "E-mail Válido"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Pessoa"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Senha"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Hash da Senha"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Redefinir Token da Senha"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Redefinir Validade do Token da Senha"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Pessoa"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Usuário"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Entrar"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Chave"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Usuário"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Usuário Web"
+
+#, fuzzy
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Sessão de Usuário Web"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Tentativa de autenticação do usuário Web"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Sessão de Usuário Web"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "O botão não está funcionando? Cole isso em seu navegador:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "Se você não fez esta solicitação, você pode ignorar esse email."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Redefinir senha"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "Nós recebemos uma solicitação para redefinir a senha da sua conta,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Botão não está funcionando? Cole isso no seu navegador:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email de Validação"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Se você recebeu esse email por engano, apenas ignore-o."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Apenas mais um passo..."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Validar"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Redefinir senha"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validar E-mail"
diff --git a/modules/web_user/locale/ro.po b/modules/web_user/locale/ro.po
new file mode 100644
index 0000000000..6bcdacc522
--- /dev/null
+++ b/modules/web_user/locale/ro.po
@@ -0,0 +1,164 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Parte"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Parola"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Parte"
+
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Resetare parola"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Resetare Parola"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr ""
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr ""
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr ""
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr ""
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr ""
diff --git a/modules/web_user/locale/ru.po b/modules/web_user/locale/ru.po
new file mode 100644
index 0000000000..f35f853751
--- /dev/null
+++ b/modules/web_user/locale/ru.po
@@ -0,0 +1,176 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Организации"
+
+#, fuzzy
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Пароль"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Организации"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Пользователь"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Логин"
+
+#, fuzzy
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Ключ"
+
+#, fuzzy
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Пользователь"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Утвержденный"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/sl.po b/modules/web_user/locale/sl.po
new file mode 100644
index 0000000000..092d9027d0
--- /dev/null
+++ b/modules/web_user/locale/sl.po
@@ -0,0 +1,170 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "e-naslov"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "Žeton e-naslova"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "e-naslov veljaven"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "Partner"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "Geslo"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "Zgoščena vrednost gesla"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "Žeton za ponastavitev gesla"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "Zapadlost ponastavitvenga žetona"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "Partner"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Uporabnik"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "Prijava"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "Ključ"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "Uporabnik"
+
+#, fuzzy
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+#, fuzzy
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Spletni uporabnik"
+
+#, fuzzy
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "Seja spletnega uporabnika"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Poskus overjanja spletnega uporabnika"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "Seja spletnega uporabnika"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Gumb ne deluje? Prilepite sledeče v vaš brskalnik:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "Če niste zahtevali tega sporočila, ga preprosto prezrite."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Ponastavitev gesla"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "Prejeli smo zahtevek za ponastavitev gesla vašega računa,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "Gumb ne deluje? Prilepite sledeče v vaš brskalnik:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Preverjanje elektronskega naslova"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "Če ste po pomoti prejeli to sporočilo, ga preprosto prezrite."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "Samo še en korak ..."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "Preveri veljavnost"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Ponastavi geslo"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Preveri e-naslov"
diff --git a/modules/web_user/locale/tr.po b/modules/web_user/locale/tr.po
new file mode 100644
index 0000000000..11696b5f66
--- /dev/null
+++ b/modules/web_user/locale/tr.po
@@ -0,0 +1,169 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr ""
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr ""
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr ""
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr ""
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr ""
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr ""
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr ""
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "Web Users"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr ""
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr ""
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr ""
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr ""
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr ""
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web Users"
+
+#, fuzzy
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web Users"
+
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr ""
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr ""
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr ""
+
+#, fuzzy
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "Email Validation"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr ""
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr ""
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "Reset Password"
+
+#, fuzzy
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "Validate E-mail"
diff --git a/modules/web_user/locale/zh_CN.po b/modules/web_user/locale/zh_CN.po
new file mode 100644
index 0000000000..7cf6270497
--- /dev/null
+++ b/modules/web_user/locale/zh_CN.po
@@ -0,0 +1,166 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgctxt "field:web.user,email:"
+msgid "E-mail"
+msgstr "电子邮件"
+
+msgctxt "field:web.user,email_token:"
+msgid "E-mail Token"
+msgstr "电子邮件令牌"
+
+msgctxt "field:web.user,email_valid:"
+msgid "E-mail Valid"
+msgstr "电子邮件有效"
+
+msgctxt "field:web.user,party:"
+msgid "Party"
+msgstr "参与者"
+
+msgctxt "field:web.user,password:"
+msgid "Password"
+msgstr "密码"
+
+msgctxt "field:web.user,password_hash:"
+msgid "Password Hash"
+msgstr "密码HASH"
+
+msgctxt "field:web.user,reset_password_token:"
+msgid "Reset Password Token"
+msgstr "重置密码令牌"
+
+msgctxt "field:web.user,reset_password_token_expire:"
+msgid "Reset Password Token Expire"
+msgstr "重置密码令牌过期"
+
+msgctxt "field:web.user,secondary_parties:"
+msgid "Secondary Parties"
+msgstr ""
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,party:"
+msgid "Party"
+msgstr "参与者"
+
+#, fuzzy
+msgctxt "field:web.user-party.party.secondary,user:"
+msgid "User"
+msgstr "用户"
+
+msgctxt "field:web.user.authenticate.attempt,device_cookie:"
+msgid "Device Cookie"
+msgstr ""
+
+msgctxt "field:web.user.authenticate.attempt,ip_address:"
+msgid "IP Address"
+msgstr "IP地址"
+
+msgctxt "field:web.user.authenticate.attempt,ip_network:"
+msgid "IP Network"
+msgstr "IP网络"
+
+msgctxt "field:web.user.authenticate.attempt,login:"
+msgid "Login"
+msgstr "登录"
+
+msgctxt "field:web.user.session,key:"
+msgid "Key"
+msgstr "密匙"
+
+msgctxt "field:web.user.session,user:"
+msgid "User"
+msgstr "用户"
+
+msgctxt "model:ir.action,name:act_user_form"
+msgid "Web Users"
+msgstr "Web 用户"
+
+msgctxt "model:ir.action,name:report_email_reset_password"
+msgid "Reset Password"
+msgstr "重置密码"
+
+msgctxt "model:ir.action,name:report_email_validation"
+msgid "Email Validation"
+msgstr "电子邮件验证"
+
+msgctxt "model:ir.message,text:msg_user_email_unique"
+msgid "E-mail of active web user must be unique."
+msgstr "已经激活的Web用户,电子邮件不能重复."
+
+msgctxt "model:ir.message,text:msg_user_session_key_unique"
+msgid "Web user session key must be unique."
+msgstr "Web用户会话密钥必须唯一."
+
+msgctxt "model:ir.model.button,string:user_reset_password_button"
+msgid "Reset Password"
+msgstr "重置密码"
+
+msgctxt "model:ir.model.button,string:user_validate_email_button"
+msgid "Validate E-mail"
+msgstr "验证电子邮件"
+
+msgctxt "model:ir.ui.menu,name:menu_user_form"
+msgid "Web Users"
+msgstr "Web 用户"
+
+msgctxt "model:web.user,name:"
+msgid "Web User"
+msgstr "Web 用户"
+
+#, fuzzy
+msgctxt "model:web.user-party.party.secondary,name:"
+msgid "Web User - Secondary Party"
+msgstr "web 用户会话"
+
+msgctxt "model:web.user.authenticate.attempt,name:"
+msgid "Web User Authenticate Attempt"
+msgstr "Web用户验证尝试"
+
+msgctxt "model:web.user.session,name:"
+msgid "Web User Session"
+msgstr "web 用户会话"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "按钮不起作用? 将此粘贴到浏览器的地址栏中:"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "If you didn't make this request, you can ignore this email."
+msgstr "如果你没有发出过此请求,就忽略此电子邮件."
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "Reset Password"
+msgstr "重置密码"
+
+msgctxt "report:web.user.email_reset_password:"
+msgid "We received a request to reset the password for your account,"
+msgstr "我们收到了重置你的帐户密码的请求,"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Button is not working? Paste this into your browser:"
+msgstr "按钮不起作用? 将此粘贴到浏览器的地址栏中:"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Email Validation"
+msgstr "电子邮件验证"
+
+msgctxt "report:web.user.email_validation:"
+msgid "If you received this email by mistake, just ignore it."
+msgstr "如果误收到此电子邮件,请忽略它。"
+
+msgctxt "report:web.user.email_validation:"
+msgid "Just one more step.."
+msgstr "仅一步之遥.."
+
+msgctxt "report:web.user.email_validation:"
+msgid "Validate"
+msgstr "验证"
+
+msgctxt "view:web.user:"
+msgid "Reset Password"
+msgstr "重置密码"
+
+msgctxt "view:web.user:"
+msgid "Validate E-mail"
+msgstr "验证电子邮件"
diff --git a/modules/web_user/message.xml b/modules/web_user/message.xml
new file mode 100644
index 0000000000..809c5f6574
--- /dev/null
+++ b/modules/web_user/message.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ E-mail of active web user must be unique.
+
+
+ Web user session key must be unique.
+
+
+
diff --git a/modules/web_user/party.py b/modules/web_user/party.py
new file mode 100644
index 0000000000..e97d2b6dc9
--- /dev/null
+++ b/modules/web_user/party.py
@@ -0,0 +1,28 @@
+# 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 PoolMeta, Pool
+
+
+class Replace(metaclass=PoolMeta):
+ __name__ = 'party.replace'
+
+ @classmethod
+ def fields_to_replace(cls):
+ return super().fields_to_replace() + [
+ ('web.user', 'party'),
+ ('web.user-party.party.secondary', 'party'),
+ ]
+
+
+class Erase(metaclass=PoolMeta):
+ __name__ = 'party.erase'
+
+ def to_erase(self, party_id):
+ pool = Pool()
+ User = pool.get('web.user')
+ to_erase = super().to_erase(party_id)
+ to_erase.append(
+ (User, [('party', '=', party_id)], True,
+ ['email'],
+ [None]))
+ return to_erase
diff --git a/modules/web_user/setup.py b/modules/web_user/setup.py
new file mode 100755
index 0000000000..7f3bd44922
--- /dev/null
+++ b/modules/web_user/setup.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import io
+import os
+import re
+from configparser import ConfigParser
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+config = ConfigParser()
+config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
+info = dict(config.items('tryton'))
+for key in ('depends', 'extras_depend', 'xml'):
+ if key in info:
+ info[key] = info[key].strip().splitlines()
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+name = 'trytond_web_user'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = (
+ 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+ name[8:], name, version))
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+requires = []
+for dep in info.get('depends', []):
+ if not re.match(r'(ir|res)(\W|$)', dep):
+ requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+
+tests_require = []
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Tryton module to manage Web users',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/modules/web_user',
+ },
+ keywords='web user',
+ package_dir={'trytond.modules.web_user': '.'},
+ packages=(
+ ['trytond.modules.web_user']
+ + ['trytond.modules.web_user.%s' % p for p in find_packages()]
+ ),
+ package_data={
+ 'trytond.modules.web_user': (info.get('xml', [])
+ + ['tryton.cfg', 'view/*.xml', 'locale/*.po', '*.html',
+ 'icons/*.svg', 'tests/*.rst']),
+ },
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU General Public License v3 or later (GPLv3+)',
+ 'Natural Language :: Bulgarian',
+ 'Natural Language :: Catalan',
+ 'Natural Language :: Czech',
+ 'Natural Language :: Dutch',
+ 'Natural Language :: English',
+ 'Natural Language :: Finnish',
+ 'Natural Language :: French',
+ 'Natural Language :: German',
+ 'Natural Language :: Hungarian',
+ 'Natural Language :: Indonesian',
+ 'Natural Language :: Italian',
+ 'Natural Language :: Persian',
+ 'Natural Language :: Polish',
+ 'Natural Language :: Portuguese (Brazilian)',
+ 'Natural Language :: Romanian',
+ 'Natural Language :: Russian',
+ 'Natural Language :: Slovenian',
+ 'Natural Language :: Spanish',
+ 'Natural Language :: Turkish',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ license='GPL-3',
+ python_requires='>=3.6',
+ install_requires=requires,
+ extras_require={
+ 'BCrypt': ['bcrypt'],
+ 'html2text': ['html2text'],
+ },
+ dependency_links=dependency_links,
+ zip_safe=False,
+ entry_points="""
+ [trytond.modules]
+ web_user = trytond.modules.web_user
+ """,
+ test_suite='tests',
+ test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
+ )
diff --git a/modules/web_user/tests/__init__.py b/modules/web_user/tests/__init__.py
new file mode 100644
index 0000000000..6c97729254
--- /dev/null
+++ b/modules/web_user/tests/__init__.py
@@ -0,0 +1,9 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+try:
+ from trytond.modules.web_user.tests.test_web_user import suite
+except ImportError:
+ from .test_web_user import suite
+
+__all__ = ['suite']
diff --git a/modules/web_user/tests/test_web_user.py b/modules/web_user/tests/test_web_user.py
new file mode 100644
index 0000000000..10e003a2d2
--- /dev/null
+++ b/modules/web_user/tests/test_web_user.py
@@ -0,0 +1,208 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+import unittest
+
+from unittest.mock import patch, ANY
+
+from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.tests.test_tryton import suite as test_suite
+from trytond.pool import Pool
+from trytond.config import config
+
+from trytond.modules.party.tests import PartyCheckEraseMixin
+from trytond.modules.web_user import user as user_module
+
+VALIDATION_URL = 'http://www.example.com/validation'
+RESET_PASSWORD_URL = 'http://www.example.com/reset_password'
+FROM = 'tryton@example.com'
+
+
+class WebUserTestCase(PartyCheckEraseMixin, ModuleTestCase):
+ 'Test Web User module'
+ module = 'web_user'
+
+ def setUp(self):
+ super(WebUserTestCase, self).setUp()
+ validation_url = config.get('web', 'email_validation_url', default='')
+ config.set('web', 'email_validation_url', VALIDATION_URL)
+ self.addCleanup(
+ lambda: config.set('web', 'email_validation_url', validation_url))
+ reset_password_url = config.get(
+ 'web', 'reset_password_url', default='')
+ config.set('web', 'reset_password_url', RESET_PASSWORD_URL)
+ self.addCleanup(lambda: config.set(
+ 'web', 'reset_password_url', reset_password_url))
+ reset_from = config.get('email', 'from', default='')
+ config.set('email', 'from', FROM)
+ self.addCleanup(lambda: config.set('email', 'from', reset_from))
+
+ length = config.get('password', 'length', default='')
+ config.set('password', 'length', '4')
+ self.addCleanup(config.set, 'password', 'length', length)
+
+ entropy = config.get('password', 'entropy', default='')
+ config.set('password', 'entropy', '0.8')
+ self.addCleanup(config.set, 'password', 'entropy', entropy)
+
+ def create_user(self, email, password):
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email=email)
+ user.password = password
+ user.save()
+
+ self.assertEqual(user.password, 'x' * 10)
+ self.assertNotEqual(user.password_hash, password)
+
+ def check_user(self, email, password):
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user, = User.search([('email', '=', email)])
+ auth_user = User.authenticate(email, password)
+ self.assertEqual(auth_user, user)
+
+ bad_user = User.authenticate(email, password + 'wrong')
+ self.assertEqual(bad_user, None)
+
+ bad_user = User.authenticate(email + 'wrong', password)
+ self.assertEqual(bad_user, None)
+
+ @with_transaction()
+ def test_default_hash(self):
+ 'Test default hash'
+ self.create_user('user@example.com', 'secret')
+ self.check_user('user@example.com', 'secret')
+
+ @with_transaction()
+ def test_session(self):
+ 'Test session'
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email='user@example.com')
+ user.save()
+
+ key = user.new_session()
+ self.assertTrue(key)
+
+ session_user = User.get_user(key)
+ self.assertEqual(session_user, user)
+
+ wrong_session_user = User.get_user('foo')
+ self.assertEqual(wrong_session_user, None)
+
+ @with_transaction()
+ def test_validate_email(self):
+ 'Test email validation'
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email='user@example.com')
+ user.save()
+ self.assertFalse(user.email_valid)
+ self.assertFalse(user.email_token)
+
+ with patch.object(user_module, 'sendmail_transactional') as sendmail:
+ User.validate_email([user])
+ sendmail.assert_called_once_with(FROM, ['user@example.com'], ANY)
+
+ token = user.email_token
+ self.assertTrue(token)
+
+ self.assertEqual(
+ user.get_email_validation_url(),
+ '%s?token=%s' % (VALIDATION_URL, token))
+
+ self.assertFalse(
+ User.validate_email_url(VALIDATION_URL))
+ self.assertFalse(
+ User.validate_email_url('%s?token=12345' % VALIDATION_URL))
+
+ self.assertTrue(
+ User.validate_email_url(user.get_email_validation_url()))
+
+ self.assertTrue(user.email_valid)
+ self.assertFalse(user.email_token)
+
+ @with_transaction()
+ def test_reset_password(self):
+ 'Test reset password'
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email='user@example.com', email_valid=True)
+ user.save()
+ self.assertFalse(user.password_hash)
+
+ with patch.object(user_module, 'sendmail_transactional') as sendmail:
+ User.reset_password([user])
+ sendmail.assert_called_once_with(FROM, ['user@example.com'], ANY)
+
+ token = user.reset_password_token
+ self.assertTrue(token)
+ self.assertTrue(user.reset_password_token_expire)
+
+ self.assertEqual(
+ user.get_email_reset_password_url(),
+ '%s?email=user%%40example.com&token=%s'
+ % (RESET_PASSWORD_URL, token))
+
+ self.assertFalse(
+ User.set_password_token('foo@example.com', '12345', 'secret'))
+ self.assertFalse(
+ User.set_password_token('user@example.com', '12345', 'secret'))
+ self.assertFalse(
+ User.set_password_token('foo@example.com', token, 'secret'))
+ self.assertFalse(user.password_hash)
+
+ self.assertTrue(User.set_password_url(
+ user.get_email_reset_password_url(), 'secret'))
+
+ self.assertTrue(user.password_hash)
+ self.assertFalse(user.reset_password_token)
+ self.assertFalse(user.reset_password_token_expire)
+
+ @with_transaction()
+ def test_create_format_email(self):
+ "Test create format email"
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email='FOO@Example.com')
+ user.save()
+
+ self.assertEqual(user.email, 'foo@example.com')
+
+ @with_transaction()
+ def test_write_format_email(self):
+ "Test write format email"
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email='foo@example.com')
+ user.save()
+ User.write([user], {'email': 'BAR@Example.com'})
+
+ self.assertEqual(user.email, 'bar@example.com')
+
+ @with_transaction()
+ def test_authenticate_case_insensitive(self):
+ "Test authenticate case insensitive"
+ pool = Pool()
+ User = pool.get('web.user')
+
+ user = User(email='foo@example.com', password='secret')
+ user.save()
+ auth_user = User.authenticate('FOO@Example.com', 'secret')
+
+ self.assertEqual(auth_user, user)
+
+
+def suite():
+ suite = test_suite()
+ suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
+ WebUserTestCase))
+ return suite
diff --git a/modules/web_user/tox.ini b/modules/web_user/tox.ini
new file mode 100644
index 0000000000..197496c0cb
--- /dev/null
+++ b/modules/web_user/tox.ini
@@ -0,0 +1,15 @@
+[tox]
+envlist = {py36,py37,py38,py39}-{sqlite,postgresql},pypy3-{sqlite,postgresql}
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+ {py36,py37,py38,py39}-postgresql: psycopg2 >= 2.5
+ pypy3-postgresql: psycopg2cffi >= 2.5
+ py36-sqlite: sqlitebck
+setenv =
+ sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
+ postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
+ sqlite: DB_NAME={env:SQLITE_NAME::memory:}
+ postgresql: DB_NAME={env:POSTGRESQL_NAME:test}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/modules/web_user/tryton.cfg b/modules/web_user/tryton.cfg
new file mode 100644
index 0000000000..5fb8ca5a0e
--- /dev/null
+++ b/modules/web_user/tryton.cfg
@@ -0,0 +1,8 @@
+[tryton]
+version=6.0.1
+depends:
+ ir
+ party
+xml:
+ user.xml
+ message.xml
diff --git a/modules/web_user/user.py b/modules/web_user/user.py
new file mode 100644
index 0000000000..861d54331b
--- /dev/null
+++ b/modules/web_user/user.py
@@ -0,0 +1,517 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import datetime
+import hashlib
+import logging
+import random
+import string
+import time
+import urllib.parse
+import warnings
+from email.header import Header
+
+try:
+ import bcrypt
+except ImportError:
+ bcrypt = None
+try:
+ import html2text
+except ImportError:
+ html2text = None
+from sql import Literal
+from sql.conditionals import Coalesce
+from sql.functions import CurrentTimestamp
+from sql.operators import Equal
+
+from trytond.config import config
+from trytond.exceptions import RateLimitException
+from trytond.model import (
+ ModelView, ModelSQL, DeactivableMixin, fields, Unique, Exclude,
+ avatar_mixin)
+from trytond.pool import Pool
+from trytond.pyson import Eval
+from trytond.report import Report, get_email
+from trytond.transaction import Transaction
+from trytond.sendmail import sendmail_transactional
+
+from trytond.ir.session import token_hex
+from trytond.res.user import LoginAttempt, CRYPT_CONTEXT
+
+logger = logging.getLogger(__name__)
+
+
+def _send_email(from_, users, email_func):
+ from_cfg = config.get('email', 'from')
+ for user in users:
+ msg, title = email_func(user)
+ msg['From'] = from_ or from_cfg
+ msg['To'] = user.email
+ msg['Subject'] = Header(title, 'utf-8')
+ sendmail_transactional(from_cfg, [user.email], msg)
+
+
+def _add_params(url, **params):
+ parts = urllib.parse.urlsplit(url)
+ query = urllib.parse.parse_qsl(parts.query)
+ for key, value in sorted(params.items()):
+ query.append((key, value))
+ parts = list(parts)
+ parts[3] = urllib.parse.urlencode(query)
+ return urllib.parse.urlunsplit(parts)
+
+
+def _extract_params(url):
+ return urllib.parse.parse_qsl(urllib.parse.urlsplit(url).query)
+
+
+class User(avatar_mixin(100), DeactivableMixin, ModelSQL, ModelView):
+ 'Web User'
+ __name__ = 'web.user'
+ _rec_name = 'email'
+
+ email = fields.Char('E-mail', select=True,
+ states={
+ 'required': Eval('active', True),
+ },
+ depends=['active'])
+ email_valid = fields.Boolean('E-mail Valid')
+ email_token = fields.Char('E-mail Token', select=True)
+ password_hash = fields.Char('Password Hash')
+ password = fields.Function(
+ fields.Char('Password'), 'get_password', setter='set_password')
+ reset_password_token = fields.Char('Reset Password Token', select=True)
+ reset_password_token_expire = fields.Timestamp(
+ 'Reset Password Token Expire')
+ party = fields.Many2One('party.party', 'Party', ondelete='RESTRICT')
+ secondary_parties = fields.Many2Many(
+ 'web.user-party.party.secondary', 'user', 'party', "Secondary Parties")
+
+ @classmethod
+ def __setup__(cls):
+ super(User, cls).__setup__()
+ table = cls.__table__()
+ cls._sql_constraints += [
+ ('email_exclude',
+ Exclude(table, (table.email, Equal),
+ where=table.active == Literal(True)),
+ 'web_user.msg_user_email_unique'),
+ ]
+ cls._buttons.update({
+ 'validate_email': {
+ 'readonly': Eval('email_valid', False),
+ 'depends': ['email_valid'],
+ },
+ 'reset_password': {
+ 'readonly': ~Eval('email_valid', False),
+ 'depends': ['email_valid'],
+ },
+ })
+
+ @classmethod
+ def __register__(cls, module_name):
+ super(User, cls).__register__(module_name)
+
+ table_h = cls.__table_handler__(module_name)
+
+ # Migration from 4.6
+ table_h.not_null_action('email', 'remove')
+
+ # Migration from 4.6: replace unique by exclude
+ table_h.drop_constraint('email_unique')
+
+ @classmethod
+ def default_email_valid(cls):
+ return False
+
+ def get_password(self, name):
+ return 'x' * 10
+
+ @classmethod
+ def set_password(cls, users, name, value):
+ pool = Pool()
+ User = pool.get('res.user')
+
+ if value == 'x' * 10:
+ return
+
+ if Transaction().user and value:
+ User.validate_password(value, users)
+
+ to_write = []
+ for user in users:
+ to_write.extend([[user], {
+ 'password_hash': cls.hash_password(value),
+ }])
+ cls.write(*to_write)
+
+ @classmethod
+ def _format_email(cls, users):
+ for user in users:
+ email = user.email.lower()
+ if email != user.email:
+ user.email = email
+ cls.save(users)
+
+ @classmethod
+ def create(cls, vlist):
+ users = super(User, cls).create(vlist)
+ cls._format_email(users)
+ return users
+
+ @classmethod
+ def write(cls, *args):
+ super(User, cls).write(*args)
+ users = sum(args[0:None:2], [])
+ cls._format_email(users)
+
+ @classmethod
+ def authenticate(cls, email, password):
+ pool = Pool()
+ Attempt = pool.get('web.user.authenticate.attempt')
+ email = email.lower()
+
+ count_ip = Attempt.count_ip()
+ if count_ip > config.getint(
+ 'session', 'max_attempt_ip_network', default=300):
+ # Do not add attempt as the goal is to prevent flooding
+ raise RateLimitException()
+ count = Attempt.count(email)
+ if count > config.getint('session', 'max_attempt', default=5):
+ Attempt.add(email)
+ raise RateLimitException()
+ # Prevent brute force attack
+ Transaction().atexit(time.sleep, 2 ** count - 1)
+
+ users = cls.search([('email', '=', email)])
+ if users:
+ user, = users
+ valid, new_hash = cls.check_password(password, user.password_hash)
+ if valid:
+ if new_hash:
+ logger.info("Update password hash for %s", user.id)
+ with Transaction().new_transaction() as transaction:
+ with transaction.set_user(0):
+ cls.write([cls(user.id)], {
+ 'password_hash': new_hash,
+ })
+ Attempt.remove(email)
+ return user
+ Attempt.add(email)
+
+ @classmethod
+ def hash_password(cls, password):
+ '''Hash given password in the form
+ $$...'''
+ if not password:
+ return ''
+ return CRYPT_CONTEXT.hash(password)
+
+ @classmethod
+ def check_password(cls, password, hash_):
+ if not hash_:
+ return False, None
+ try:
+ return CRYPT_CONTEXT.verify_and_update(password, hash_)
+ except ValueError:
+ hash_method = hash_.split('$', 1)[0]
+ warnings.warn(
+ "Use deprecated hash method %s" % hash_method,
+ DeprecationWarning)
+ valid = getattr(cls, 'check_' + hash_method)(password, hash_)
+ if valid:
+ new_hash = CRYPT_CONTEXT.hash(password)
+ else:
+ new_hash = None
+ return valid, new_hash
+
+ @classmethod
+ def hash_sha1(cls, password):
+ salt = ''.join(random.sample(string.ascii_letters + string.digits, 8))
+ salted_password = password + salt
+ if isinstance(salted_password, str):
+ salted_password = salted_password.encode('utf-8')
+ hash_ = hashlib.sha1(salted_password).hexdigest()
+ return '$'.join(['sha1', hash_, salt])
+
+ @classmethod
+ def check_sha1(cls, password, hash_):
+ if isinstance(password, str):
+ password = password.encode('utf-8')
+ hash_method, hash_, salt = hash_.split('$', 2)
+ salt = salt or ''
+ if isinstance(salt, str):
+ salt = salt.encode('utf-8')
+ assert hash_method == 'sha1'
+ return hash_ == hashlib.sha1(password + salt).hexdigest()
+
+ @classmethod
+ def hash_bcrypt(cls, password):
+ if isinstance(password, str):
+ password = password.encode('utf-8')
+ hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
+ return '$'.join(['bcrypt', hash_])
+
+ @classmethod
+ def check_bcrypt(cls, password, hash_):
+ if isinstance(password, str):
+ password = password.encode('utf-8')
+ hash_method, hash_ = hash_.split('$', 1)
+ if isinstance(hash_, str):
+ hash_ = hash_.encode('utf-8')
+ assert hash_method == 'bcrypt'
+ return hash_ == bcrypt.hashpw(password, hash_)
+
+ def new_session(self):
+ pool = Pool()
+ Session = pool.get('web.user.session')
+ return Session.add(self)
+
+ @classmethod
+ def get_user(cls, session):
+ pool = Pool()
+ Session = pool.get('web.user.session')
+ return Session.get_user(session)
+
+ @classmethod
+ @ModelView.button
+ def validate_email(cls, users, from_=None):
+ for user in users:
+ user.set_email_token()
+ cls.save(users)
+ _send_email(from_, users, cls.get_email_validation)
+
+ def set_email_token(self, nbytes=None):
+ self.email_token = token_hex(nbytes)
+
+ def get_email_validation(self):
+ return get_email(
+ 'web.user.email_validation', self, self.languages)
+
+ def get_email_validation_url(self, url=None):
+ if url is None:
+ url = config.get('web', 'email_validation_url')
+ return _add_params(url, token=self.email_token)
+
+ @classmethod
+ def validate_email_url(cls, url):
+ parts = urllib.parse.urlsplit(url)
+ tokens = filter(
+ None, urllib.parse.parse_qs(parts.query).get('token', [None]))
+ return cls.validate_email_token(list(tokens))
+
+ @classmethod
+ def validate_email_token(cls, tokens):
+ users = cls.search([
+ ('email_token', 'in', tokens),
+ ])
+ cls.write(users, {
+ 'email_valid': True,
+ 'email_token': None,
+ })
+ return users
+
+ @classmethod
+ @ModelView.button
+ def reset_password(cls, users, from_=None):
+ now = datetime.datetime.now()
+
+ # Prevent abusive reset
+ def reset(user):
+ return not (user.reset_password_token_expire
+ and user.reset_password_token_expire > now)
+ users = list(filter(reset, users))
+
+ for user in users:
+ user.set_reset_password_token()
+ cls.save(users)
+ _send_email(from_, users, cls.get_email_reset_password)
+
+ def set_reset_password_token(self, nbytes=None):
+ self.reset_password_token = token_hex(nbytes)
+ self.reset_password_token_expire = (
+ datetime.datetime.now() + datetime.timedelta(
+ seconds=config.getint(
+ 'session', 'web_timeout_reset', default=24 * 60 * 60)))
+
+ def clear_reset_password_token(self):
+ self.reset_password_token = None
+ self.reset_password_token_expire = None
+
+ def get_email_reset_password(self):
+ return get_email(
+ 'web.user.email_reset_password', self, self.languages)
+
+ def get_email_reset_password_url(self, url=None):
+ if url is None:
+ url = config.get('web', 'reset_password_url')
+ return _add_params(
+ url, token=self.reset_password_token, email=self.email)
+
+ @classmethod
+ def set_password_url(cls, url, password):
+ parts = urllib.parse.urlsplit(url)
+ query = urllib.parse.parse_qs(parts.query)
+ email = query.get('email', [None])[0]
+ token = query.get('token', [None])[0]
+ return cls.set_password_token(email, token, password)
+
+ @classmethod
+ def set_password_token(cls, email, token, password):
+ pool = Pool()
+ Attempt = pool.get('web.user.authenticate.attempt')
+ email = email.lower()
+
+ # Prevent brute force attack
+ Transaction().atexit(
+ time.sleep, random.randint(0, 2 ** Attempt.count(email) - 1))
+
+ users = cls.search([
+ ('email', '=', email),
+ ])
+ if users:
+ user, = users
+ if user.reset_password_token == token:
+ now = datetime.datetime.now()
+ expire = user.reset_password_token_expire
+ user.clear_reset_password_token()
+ if expire > now:
+ user.password = password
+ user.save()
+ Attempt.remove(email)
+ return True
+ Attempt.add(email)
+ return False
+
+ @property
+ def languages(self):
+ pool = Pool()
+ Language = pool.get('ir.lang')
+ if self.party and self.party.lang:
+ languages = [self.party.lang]
+ else:
+ languages = Language.search([
+ ('code', '=', Transaction().language),
+ ])
+ return languages
+
+
+class User_PartySecondary(ModelSQL):
+ "Web User - Secondary Party"
+ __name__ = 'web.user-party.party.secondary'
+
+ user = fields.Many2One(
+ 'web.user', "User", required=True, select=True, ondelete='CASCADE')
+ party = fields.Many2One(
+ 'party.party', "Party", required=True, ondelete='CASCADE')
+
+
+class UserAuthenticateAttempt(LoginAttempt):
+ 'Web User Authenticate Attempt'
+ __name__ = 'web.user.authenticate.attempt'
+ _table = None # Needed to reset LoginAttempt._table
+
+
+class UserSession(ModelSQL):
+ 'Web User Session'
+ __name__ = 'web.user.session'
+ _rec_name = 'key'
+
+ key = fields.Char('Key', required=True, select=True)
+ user = fields.Many2One(
+ 'web.user', 'User', required=True, select=True, ondelete='CASCADE')
+
+ @classmethod
+ def __setup__(cls):
+ super(UserSession, cls).__setup__()
+ table = cls.__table__()
+ cls.__rpc__ = {}
+ cls._sql_constraints += [
+ ('key_unique', Unique(table, table.key),
+ 'web_user.msg_user_session_key_unique'),
+ ]
+
+ @classmethod
+ def default_key(cls, nbytes=None):
+ return token_hex(nbytes)
+
+ @classmethod
+ def add(cls, user):
+ cursor = Transaction().connection.cursor()
+ table = cls.__table__()
+
+ cursor.execute(*table.delete(
+ where=(
+ Coalesce(table.write_date, table.create_date)
+ - CurrentTimestamp()) > cls.timeout()))
+
+ session = cls(user=user)
+ session.save()
+ return session.key
+
+ @classmethod
+ def remove(cls, key):
+ sessions = cls.search([
+ ('key', '=', key),
+ ])
+ cls.delete(sessions)
+
+ @classmethod
+ def get_user(cls, session):
+ transaction = Transaction()
+ sessions = cls.search([
+ ('key', '=', session),
+ ])
+ if not sessions:
+ return
+ session, = sessions
+ if not session.expired:
+ return session.user
+ elif not transaction.readonly:
+ cls.delete([session])
+
+ @classmethod
+ def timeout(cls):
+ return datetime.timedelta(seconds=config.getint(
+ 'session', 'web_timeout', default=30 * 24 * 60 * 60))
+
+ @property
+ def expired(self):
+ now = datetime.datetime.now()
+ timestamp = self.write_date or self.create_date
+ return abs(timestamp - now) > self.timeout()
+
+ @classmethod
+ def reset(cls, session):
+ sessions = cls.search([
+ ('key', '=', session),
+ ])
+ cls.write(sessions, {})
+
+ @classmethod
+ def create(cls, vlist):
+ vlist = [v.copy() for v in vlist]
+ for values in vlist:
+ # Ensure to get a different key for each record
+ # default methods are called only once
+ values.setdefault('key', cls.default_key())
+ return super(UserSession, cls).create(vlist)
+
+
+class EmailValidation(Report):
+ __name__ = 'web.user.email_validation'
+
+ @classmethod
+ def get_context(cls, records, header, data):
+ context = super().get_context(records, header, data)
+ context['extract_params'] = _extract_params
+ return context
+
+
+class EmailResetPassword(Report):
+ __name__ = 'web.user.email_reset_password'
+
+ @classmethod
+ def get_context(cls, records, header, data):
+ context = super().get_context(records, header, data)
+ context['extract_params'] = _extract_params
+ return context
diff --git a/modules/web_user/user.xml b/modules/web_user/user.xml
new file mode 100644
index 0000000000..8cfebacdc5
--- /dev/null
+++ b/modules/web_user/user.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+ web.user
+ form
+ user_form
+
+
+
+ web.user
+ tree
+ user_list
+
+
+
+ Web Users
+ web.user
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ validate_email
+ Validate E-mail
+
+
+
+
+ reset_password
+ Reset Password
+
+
+
+
+ Email Validation
+ web.user
+ web.user.email_validation
+ web_user/email_validation.html
+ html
+
+
+ Reset Password
+ web.user
+ web.user.email_reset_password
+ web_user/email_reset_password.html
+ html
+
+
+
diff --git a/modules/web_user/view/user_form.xml b/modules/web_user/view/user_form.xml
new file mode 100644
index 0000000000..16a87b8ce9
--- /dev/null
+++ b/modules/web_user/view/user_form.xml
@@ -0,0 +1,24 @@
+
+
+
diff --git a/modules/web_user/view/user_list.xml b/modules/web_user/view/user_list.xml
new file mode 100644
index 0000000000..aaa019cf33
--- /dev/null
+++ b/modules/web_user/view/user_list.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/proteus/.drone.yml b/proteus/.drone.yml
new file mode 100644
index 0000000000..d8e4ca7b1d
--- /dev/null
+++ b/proteus/.drone.yml
@@ -0,0 +1,40 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ tox:
+ image: ${IMAGE}
+ environment:
+ - CFLAGS=-O0
+ - TOX_TESTENV_PASSENV=CFLAGS CI_BUILD_NUMBER CI_JOB_NUMBER CI_JOB_ID
+ commands:
+ - echo "[extensions]" >> /root/.hgrc
+ - echo "hgext.share =" >> /root/.hgrc
+ - echo "[share]" >> /root/.hgrc
+ - echo "pool = /root/.cache/hg" >> /root/.hgrc
+ - pip install tox pydot
+ - tox -e "${TOXENV}"
+ volumes:
+ - cache:/root/.cache
+ check_dist:
+ image: ${IMAGE}
+ commands:
+ - pip install twine
+ - python setup.py sdist
+ - twine check dist/*
+
+matrix:
+ include:
+ - IMAGE: python:3.6
+ TOXENV: py36
+ - IMAGE: python:3.7
+ TOXENV: py37
+ - IMAGE: python:3.8
+ TOXENV: py38
+ - IMAGE: python:3.9
+ TOXENV: py39
diff --git a/proteus/.flake8 b/proteus/.flake8
new file mode 100644
index 0000000000..fa6b4f66d1
--- /dev/null
+++ b/proteus/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore=E123,E124,E126,E128,E741,W503
diff --git a/proteus/CHANGELOG b/proteus/CHANGELOG
new file mode 100644
index 0000000000..23bb5c42ec
--- /dev/null
+++ b/proteus/CHANGELOG
@@ -0,0 +1,127 @@
+Version 6.0.7 - 2022-11-17
+--------------------------
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.6 - 2022-04-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.5 - 2022-03-01
+* Bug fixes (see mercurial logs for details)
+* Use defusedxml to parse XML (11244)
+
+Version 6.0.4 - 2022-01-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.3 - 2021-10-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.2 - 2021-07-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.1 - 2021-05-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+* Add support for Python 3.9
+* Unify PYSON string format
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.5
+* Support PYSON comparison of date and datetime
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+* Add support for Python 3.8
+* Use same __str__ as trytond instances
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+* Support dot notation on PYSON Eval
+* Add __slots__
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+* Remove support for Python 3.4
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Add support for Python 3.7
+* Remove support for Python 2.7
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+* Always use the context and config of the instance
+* Add support for search_* attributes in ModelList
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+* Add set_sequence method to ModelList
+* Add __int__ to Model
+* Allow to use keyword arguments with trytond configuration
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+* Fill default Report data
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Add Python3 support
+
+Version 3.8.0 - 2015-11-02
+* Bug fixes (see mercurial logs for details)
+* Add StateAction support to Wizard
+
+Version 3.6.0 - 2015-04-20
+* Bug fixes (see mercurial logs for details)
+* Add support for PyPy
+* Allow to provide extra keyword arguments to set_xmlrpc()
+* Add support of fields.TimeDelta
+* Remove password on set_trytond
+* Manage reload, save, delete, duplicate and click as class and instance method
+
+Version 3.4.0 - 2014-10-20
+* Bug fixes (see mercurial logs for details)
+* New configuration syntax
+* Add Report
+* Explicitly set value of parent field
+* Add duplicate method
+
+Version 3.2.0 - 2014-04-21
+* Bug fixes (see mercurial logs for details)
+* Models cache depends also on user
+* Drop support of Python 2.6
+* Allow to pass field values to new method
+* Add click method for buttons
+
+Version 3.0.0 - 2013-10-21
+* Bug fixes (see mercurial logs for details)
+
+Version 2.8.0 - 2013-04-22
+* Bug fixes (see mercurial logs for details)
+* Add support of fields.Dict
+
+Version 2.6.0 - 2012-10-22
+* Bug fixes (see mercurial logs for details)
+
+Version 2.4.0 - 2012-04-23
+* Bug fixes (see mercurial logs for details)
+* Use loading attribute
+* Add support of _buttons
+* Add support of fields.Time
+
+Version 2.2.0 - 2011-10-25
+* Bug fixes (see mercurial logs for details)
+* Change license to LGPL-3
+* Drop support of Python 2.5
+
+Version 2.0.0 - 2011-04-28
+* Bug fixes (see mercurial logs for details)
+* Manage default values of trytond configuration
+
+Version 1.8.0 - 2010-12-24
+* Initial release
diff --git a/proteus/COPYRIGHT b/proteus/COPYRIGHT
new file mode 100644
index 0000000000..ceaf6af071
--- /dev/null
+++ b/proteus/COPYRIGHT
@@ -0,0 +1,15 @@
+Copyright (C) 2010-2022 Cédric Krier.
+Copyright (C) 2010-2022 B2CK SPRL.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/proteus/LICENSE b/proteus/LICENSE
new file mode 100644
index 0000000000..75797f4ef8
--- /dev/null
+++ b/proteus/LICENSE
@@ -0,0 +1,841 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/proteus/MANIFEST.in b/proteus/MANIFEST.in
new file mode 100644
index 0000000000..e88b2348a3
--- /dev/null
+++ b/proteus/MANIFEST.in
@@ -0,0 +1,5 @@
+include CHANGELOG
+include COPYRIGHT
+include LICENSE
+include README.rst
+include doc/*
diff --git a/proteus/README.rst b/proteus/README.rst
new file mode 120000
index 0000000000..176d9c2695
--- /dev/null
+++ b/proteus/README.rst
@@ -0,0 +1 @@
+doc/index.rst
\ No newline at end of file
diff --git a/proteus/doc/index.rst b/proteus/doc/index.rst
new file mode 100644
index 0000000000..a5b5a37138
--- /dev/null
+++ b/proteus/doc/index.rst
@@ -0,0 +1,125 @@
+=======================
+Tryton Scripting Client
+=======================
+
+A library to access Tryton's models like a client.
+
+Example of usage
+----------------
+
+ >>> from proteus import config, Model, Wizard, Report
+
+Configuration
+~~~~~~~~~~~~~
+
+Configuration to connect to a sqlite memory database using trytond as module.
+
+ >>> config = config.set_trytond('sqlite:///:memory:')
+
+Activating a module
+~~~~~~~~~~~~~~~~~~~
+
+Find the module, call the activate button and run the upgrade wizard.
+
+ >>> Module = Model.get('ir.module')
+ >>> party_module, = Module.find([('name', '=', 'party')])
+ >>> party_module.click('activate')
+ >>> Wizard('ir.module.activate_upgrade').execute('upgrade')
+
+Creating a party
+~~~~~~~~~~~~~~~~
+
+First instanciate a new Party:
+
+ >>> Party = Model.get('party.party')
+ >>> party = Party()
+ >>> party.id < 0
+ True
+
+Fill the fields:
+
+ >>> party.name = 'ham'
+
+Save the instance into the server:
+
+ >>> party.save()
+ >>> party.name
+ 'ham'
+ >>> party.id > 0
+ True
+
+Setting the language of the party
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The language on party is a `Many2One` relation field. So it requires to get a
+`Model` instance as value.
+
+ >>> Lang = Model.get('ir.lang')
+ >>> en, = Lang.find([('code', '=', 'en')])
+ >>> party.lang = en
+ >>> party.save()
+ >>> party.lang.code
+ 'en'
+
+Creating an address for the party
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Addresses are store on party with a `One2Many` field. So the new address just
+needs to be appended to the list `addresses`.
+
+ >>> address = party.addresses.new(postal_code='42')
+ >>> party.save()
+ >>> party.addresses #doctest: +ELLIPSIS
+ [proteus.Model.get('party.address')(...)]
+
+Adding category to the party
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Categories are linked to party with a `Many2Many` field.
+
+So first create a category
+
+ >>> Category = Model.get('party.category')
+ >>> category = Category()
+ >>> category.name = 'spam'
+ >>> category.save()
+
+Append it to categories of the party
+
+ >>> party.categories.append(category)
+ >>> party.save()
+ >>> party.categories #doctest: +ELLIPSIS
+ [proteus.Model.get('party.category')(...)]
+
+Print party label
+~~~~~~~~~~~~~~~~~
+
+There is a label report on `Party`.
+
+ >>> label = Report('party.label')
+
+The report is executed with a list of records and some extra data.
+
+ >>> type_, data, print_, name = label.execute([party], {})
+
+Sorting addresses and register order
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Addresses are ordered by sequence which means they can be stored following a
+specific order. The `set_sequence` method stores the current order.
+
+ >>> address = party.addresses.new(postal_code='69')
+ >>> party.save()
+ >>> address = party.addresses.new(postal_code='23')
+ >>> party.save()
+
+Now changing the order.
+
+ >>> reversed_addresses = list(reversed(party.addresses))
+ >>> while party.addresses:
+ ... _ = party.addresses.pop()
+ >>> party.addresses.extend(reversed_addresses)
+ >>> party.addresses.set_sequence()
+ >>> party.save()
+ >>> party.addresses == reversed_addresses
+ True
diff --git a/proteus/proteus/__init__.py b/proteus/proteus/__init__.py
new file mode 100644
index 0000000000..73273b9680
--- /dev/null
+++ b/proteus/proteus/__init__.py
@@ -0,0 +1,1356 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+'''
+A library to access Tryton's models like a client.
+'''
+import threading
+import datetime
+import functools
+from decimal import Decimal
+
+import proteus.config
+
+__version__ = "6.0.8"
+__all__ = ['Model', 'Wizard', 'Report']
+
+_MODELS = threading.local()
+
+
+class _EvalEnvironment(dict):
+ 'Dictionary for evaluation'
+ __slots__ = ('parent', 'eval_type')
+
+ def __init__(self, parent, eval_type='eval'):
+ super(_EvalEnvironment, self).__init__()
+ self.parent = parent
+ assert eval_type in ('eval', 'on_change')
+ self.eval_type = eval_type
+
+ def __getitem__(self, item):
+ if item == 'id':
+ return self.parent.id
+ if item == '_parent_' + self.parent._parent_name \
+ and self.parent._parent:
+ return _EvalEnvironment(self.parent._parent,
+ eval_type=self.eval_type)
+ if self.eval_type == 'eval':
+ return self.parent._get_eval()[item]
+ else:
+ return self.parent._get_on_change_values(fields=[item])[item]
+
+ def __getattr__(self, item):
+ try:
+ return self.__getitem__(item)
+ except KeyError:
+ raise AttributeError(item)
+
+ def get(self, item, default=None):
+ try:
+ return self.__getitem__(item)
+ except KeyError:
+ pass
+ return super(_EvalEnvironment, self).get(item, default)
+
+ def __bool__(self):
+ return True
+
+ def __str__(self):
+ return str(self.parent)
+
+ def __repr__(self):
+ return repr(self.parent)
+
+ def __contains__(self, item):
+ if item == 'id':
+ return True
+ if item == '_parent_' + self.parent._parent_name \
+ and self.parent._parent:
+ return True
+ if self.eval_type == 'eval':
+ return item in self.parent._get_eval()
+ else:
+ return item in self.parent._fields
+
+
+class dualmethod(object):
+ """Descriptor implementing combination of class and instance method
+
+ When called on an instance, the class is passed as the first argument and a
+ list with the instance as the second.
+ When called on a class, the class itself is passed as the first argument.
+
+ >>> class Example(object):
+ ... @dualmethod
+ ... def method(cls, instances):
+ ... print(len(instances))
+ ...
+ >>> Example.method([Example()])
+ 1
+ >>> Example().method()
+ 1
+ """
+ __slots__ = ('func')
+
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, instance, owner):
+
+ @functools.wraps(self.func)
+ def newfunc(*args, **kwargs):
+ if instance:
+ return self.func(owner, [instance], *args, **kwargs)
+ else:
+ return self.func(owner, *args, **kwargs)
+ return newfunc
+
+
+class FieldDescriptor(object):
+ default = None
+
+ def __init__(self, name, definition):
+ super(FieldDescriptor, self).__init__()
+ self.name = name
+ self.definition = definition
+ self.__doc__ = definition['string']
+
+ def __get__(self, instance, owner):
+ if instance.id >= 0:
+ instance._read(self.name)
+ return instance._values.get(self.name, self.default)
+
+ def __set__(self, instance, value):
+ if instance.id >= 0:
+ instance._read(self.name)
+ previous = getattr(instance, self.name)
+ instance._values[self.name] = value
+ if previous != getattr(instance, self.name):
+ instance._changed.add(self.name)
+ instance._on_change([self.name])
+ if instance._parent:
+ instance._parent._changed.add(instance._parent_field_name)
+ instance._parent._on_change([instance._parent_field_name])
+
+
+class BooleanDescriptor(FieldDescriptor):
+ default = False
+
+ def __set__(self, instance, value):
+ assert isinstance(value, bool)
+ super(BooleanDescriptor, self).__set__(instance, value)
+
+
+class CharDescriptor(FieldDescriptor):
+ default = None
+
+ def __set__(self, instance, value):
+ assert isinstance(value, str) or value is None
+ super(CharDescriptor, self).__set__(instance, value or '')
+
+
+class BinaryDescriptor(FieldDescriptor):
+ default = None
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (bytes, bytearray)) or value is None
+ super(BinaryDescriptor, self).__set__(instance, value)
+
+
+class IntegerDescriptor(FieldDescriptor):
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (int, type(None)))
+ super(IntegerDescriptor, self).__set__(instance, value)
+
+
+class FloatDescriptor(FieldDescriptor):
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (int, float, Decimal, type(None)))
+ if value is not None:
+ value = float(value)
+ super(FloatDescriptor, self).__set__(instance, value)
+
+
+class NumericDescriptor(FieldDescriptor):
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (type(None), Decimal))
+ # TODO add digits validation
+ super(NumericDescriptor, self).__set__(instance, value)
+
+
+class SelectionDescriptor(FieldDescriptor):
+
+ def __set__(self, instance, value):
+ assert isinstance(value, str) or value is None
+ # TODO add selection validation
+ super().__set__(instance, value)
+
+
+class MultiSelectionDescriptor(FieldDescriptor):
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (list, type(None)))
+ # TODO add selection validation
+ super().__set__(instance, value)
+
+
+class ReferenceDescriptor(FieldDescriptor):
+ def __get__(self, instance, owner):
+ value = super(ReferenceDescriptor, self).__get__(instance, owner)
+ if isinstance(value, str):
+ model_name, id = value.split(',', 1)
+ if model_name:
+ Relation = Model.get(model_name, instance._config)
+ config = Relation._config
+ with config.reset_context(), \
+ config.set_context(instance._context):
+ value = Relation(int(id))
+ instance._values[self.name] = value
+ return value
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (Model, type(None), str))
+ if isinstance(value, str):
+ assert value.startswith(',')
+ elif isinstance(value, Model):
+ assert value._config == instance._config
+ super(ReferenceDescriptor, self).__set__(instance, value)
+
+
+class DateDescriptor(FieldDescriptor):
+ def __get__(self, instance, owner):
+ value = super(DateDescriptor, self).__get__(instance, owner)
+ if isinstance(value, datetime.datetime):
+ value = value.date()
+ instance._values[self.name] = value
+ return value
+
+ def __set__(self, instance, value):
+ assert isinstance(value, datetime.date) or value is None
+ super(DateDescriptor, self).__set__(instance, value)
+
+
+class DateTimeDescriptor(FieldDescriptor):
+ def __set__(self, instance, value):
+ assert isinstance(value, datetime.datetime) or value is None
+ super(DateTimeDescriptor, self).__set__(instance, value)
+
+
+class TimeDescriptor(FieldDescriptor):
+ def __set__(self, instance, value):
+ assert isinstance(value, datetime.time) or value is None
+ super(TimeDescriptor, self).__set__(instance, value)
+
+
+class TimeDeltaDescriptor(FieldDescriptor):
+ def __set__(self, instance, value):
+ assert isinstance(value, datetime.timedelta) or value is None
+ super(TimeDeltaDescriptor, self).__set__(instance, value)
+
+
+class DictDescriptor(FieldDescriptor):
+ def __get__(self, instance, owner):
+ value = super(DictDescriptor, self).__get__(instance, owner)
+ if value:
+ value = value.copy()
+ return value
+
+ def __set__(self, instance, value):
+ assert isinstance(value, dict) or value is None
+ super(DictDescriptor, self).__set__(instance, value)
+
+
+class Many2OneDescriptor(FieldDescriptor):
+ def __get__(self, instance, owner):
+ Relation = Model.get(self.definition['relation'], instance._config)
+ value = super(Many2OneDescriptor, self).__get__(instance, owner)
+ if isinstance(value, int):
+ config = Relation._config
+ with config.reset_context(), config.set_context(instance._context):
+ value = Relation(value)
+ if self.name in instance._values:
+ instance._values[self.name] = value
+ return value
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (Model, type(None)))
+ if value:
+ assert value._config == instance._config
+ super(Many2OneDescriptor, self).__set__(instance, value)
+
+
+class One2OneDescriptor(Many2OneDescriptor):
+ pass
+
+
+class One2ManyDescriptor(FieldDescriptor):
+ default = []
+
+ def __get__(self, instance, owner):
+ from .pyson import PYSONDecoder
+ Relation = Model.get(self.definition['relation'], instance._config)
+ value = super(One2ManyDescriptor, self).__get__(instance, owner)
+ if not isinstance(value, ModelList):
+ ctx = instance._context.copy() if instance._context else {}
+ if self.definition.get('context'):
+ decoder = PYSONDecoder(_EvalEnvironment(instance))
+ ctx.update(decoder.decode(self.definition.get('context')))
+ config = Relation._config
+ with config.reset_context(), config.set_context(ctx):
+ value = ModelList(self.definition, (Relation(id)
+ for id in value or []), instance, self.name)
+ instance._values[self.name] = value
+ return value
+
+ def __set__(self, instance, value):
+ raise AttributeError
+
+
+class Many2ManyDescriptor(One2ManyDescriptor):
+ pass
+
+
+class ValueDescriptor(object):
+ def __init__(self, name, definition):
+ super(ValueDescriptor, self).__init__()
+ self.name = name
+ self.definition = definition
+
+ def __get__(self, instance, owner):
+ return getattr(instance, self.name)
+
+
+class ReferenceValueDescriptor(ValueDescriptor):
+ def __get__(self, instance, owner):
+ value = super(ReferenceValueDescriptor, self).__get__(instance, owner)
+ if isinstance(value, Model):
+ value = '%s,%s' % (value.__class__.__name__, value.id)
+ return value or None
+
+
+class Many2OneValueDescriptor(ValueDescriptor):
+ def __get__(self, instance, owner):
+ value = super(Many2OneValueDescriptor, self).__get__(instance, owner)
+ return value and value.id or None
+
+
+class One2OneValueDescriptor(Many2OneValueDescriptor):
+ pass
+
+
+class One2ManyValueDescriptor(ValueDescriptor):
+ def __get__(self, instance, owner):
+ value = []
+ value_list = getattr(instance, self.name)
+ parent_name = self.definition.get('relation_field', '')
+ to_add = []
+ to_create = []
+ to_write = []
+ for record in value_list:
+ if record.id >= 0:
+ values = record._get_values(fields=record._changed)
+ values.pop(parent_name, None)
+ if record._changed and values:
+ to_write.extend(([record.id], values))
+ to_add.append(record.id)
+ else:
+ values = record._get_values()
+ values.pop(parent_name, None)
+ to_create.append(values)
+ if to_add:
+ value.append(('add', to_add))
+ if to_create:
+ value.append(('create', to_create))
+ if to_write:
+ value.append(('write',) + tuple(to_write))
+ if value_list.record_removed:
+ value.append(('remove', [x.id for x in value_list.record_removed]))
+ if value_list.record_deleted:
+ value.append(('delete', [x.id for x in value_list.record_deleted]))
+ return value
+
+
+class Many2ManyValueDescriptor(One2ManyValueDescriptor):
+ pass
+
+
+class EvalDescriptor(object):
+ def __init__(self, name, definition):
+ super(EvalDescriptor, self).__init__()
+ self.name = name
+ self.definition = definition
+
+ def __get__(self, instance, owner):
+ return getattr(instance, self.name)
+
+
+class ReferenceEvalDescriptor(EvalDescriptor):
+ def __get__(self, instance, owner):
+ value = super(ReferenceEvalDescriptor, self).__get__(instance, owner)
+ if isinstance(value, Model):
+ value = '%s,%s' % (value.__class__.__name__, value.id)
+ return value or None
+
+
+class Many2OneEvalDescriptor(EvalDescriptor):
+ def __get__(self, instance, owner):
+ value = super(Many2OneEvalDescriptor, self).__get__(instance, owner)
+ if value:
+ return value.id
+ return None
+
+
+class One2OneEvalDescriptor(Many2OneEvalDescriptor):
+ pass
+
+
+class One2ManyEvalDescriptor(EvalDescriptor):
+ def __get__(self, instance, owner):
+ # Directly use _values to prevent infinite recursion with
+ # One2ManyDescriptor which could evaluate this field to decode the
+ # context
+ value = instance._values.get(self.name, [])
+ if isinstance(value, ModelList):
+ return [x.id for x in value]
+ else:
+ return value
+
+
+class Many2ManyEvalDescriptor(One2ManyEvalDescriptor):
+ pass
+
+
+class MetaModelFactory(object):
+ descriptors = {
+ 'boolean': BooleanDescriptor,
+ 'char': CharDescriptor,
+ 'text': CharDescriptor,
+ 'binary': BinaryDescriptor,
+ 'selection': SelectionDescriptor,
+ 'multiselection': MultiSelectionDescriptor,
+ 'integer': IntegerDescriptor,
+ 'biginteger': IntegerDescriptor,
+ 'float': FloatDescriptor,
+ 'numeric': NumericDescriptor,
+ 'reference': ReferenceDescriptor,
+ 'date': DateDescriptor,
+ 'datetime': DateTimeDescriptor,
+ 'timestamp': DateTimeDescriptor,
+ 'time': TimeDescriptor,
+ 'timedelta': TimeDeltaDescriptor,
+ 'dict': DictDescriptor,
+ 'many2one': Many2OneDescriptor,
+ 'one2many': One2ManyDescriptor,
+ 'many2many': Many2ManyDescriptor,
+ 'one2one': One2OneDescriptor,
+ }
+ value_descriptors = {
+ 'reference': ReferenceValueDescriptor,
+ 'many2one': Many2OneValueDescriptor,
+ 'one2many': One2ManyValueDescriptor,
+ 'many2many': Many2ManyValueDescriptor,
+ 'one2one': One2OneValueDescriptor,
+ }
+ eval_descriptors = {
+ 'reference': ReferenceEvalDescriptor,
+ 'many2one': Many2OneEvalDescriptor,
+ 'one2many': One2ManyEvalDescriptor,
+ 'many2many': Many2ManyEvalDescriptor,
+ 'one2one': One2OneEvalDescriptor,
+ }
+
+ def __init__(self, model_name, config=None):
+ super(MetaModelFactory, self).__init__()
+ self.model_name = model_name
+ self.config = config or proteus.config.get_config()
+
+ def __call__(self):
+ models_key = 'c%su%s' % (id(self.config), self.config.user)
+ if not hasattr(_MODELS, models_key):
+ setattr(_MODELS, models_key, {})
+
+ class MetaModel(type):
+ 'Meta class for Model'
+ __slots__ = ()
+
+ def __new__(mcs, name, bases, dict):
+ if self.model_name in getattr(_MODELS, models_key):
+ return getattr(_MODELS, models_key)[self.model_name]
+ proxy = self.config.get_proxy(self.model_name)
+ context = self.config.context
+ name = self.model_name
+ dict['_proxy'] = proxy
+ dict['_config'] = self.config
+ dict['_fields'] = proxy.fields_get(None, context)
+ for field_name, definition in dict['_fields'].items():
+ if field_name == 'id':
+ continue
+ Descriptor = self.descriptors.get(definition['type'],
+ FieldDescriptor)
+ dict[field_name] = Descriptor(field_name, definition)
+ VDescriptor = self.value_descriptors.get(
+ definition['type'], ValueDescriptor)
+ dict['__%s_value' % field_name] = VDescriptor(
+ field_name, definition)
+ EDescriptor = self.eval_descriptors.get(
+ definition['type'], EvalDescriptor)
+ dict['__%s_eval' % field_name] = EDescriptor(
+ field_name, definition)
+ for method in self.config.get_proxy_methods(self.model_name):
+ setattr(mcs, method, getattr(proxy, method))
+ res = type.__new__(mcs, name, bases, dict)
+ getattr(_MODELS, models_key)[self.model_name] = res
+ return res
+ __new__.__doc__ = type.__new__.__doc__
+ return MetaModel
+
+
+class ModelList(list):
+ 'List for Model'
+ __slots__ = ('model_name', 'parent', 'parent_field_name', 'parent_name',
+ 'domain', 'context', 'add_remove', 'search_order', 'search_context',
+ 'record_removed', 'record_deleted')
+
+ def __init__(self, definition, sequence=None, parent=None,
+ parent_field_name=''):
+ self.model_name = definition['relation']
+ if sequence is None:
+ sequence = []
+ self.parent = parent
+ if parent:
+ assert parent_field_name
+ self.parent_field_name = parent_field_name
+ self.parent_name = definition.get('relation_field', '')
+ self.domain = definition.get('domain', [])
+ self.context = definition.get('context')
+ self.add_remove = definition.get('add_remove')
+ self.search_order = definition.get('search_order', 'null')
+ self.search_context = definition.get('search_context', '{}')
+ self.record_removed = set()
+ self.record_deleted = set()
+ result = super(ModelList, self).__init__(sequence)
+ self.__check(self, on_change=False)
+ return result
+ __init__.__doc__ = list.__init__.__doc__
+
+ def _changed(self):
+ 'Signal change to parent'
+ if self.parent:
+ self.parent._changed.add(self.parent_field_name)
+ self.parent._on_change([self.parent_field_name])
+
+ def _get_context(self):
+ from .pyson import PYSONDecoder
+ decoder = PYSONDecoder(_EvalEnvironment(self.parent))
+ ctx = self.parent._context.copy() if self.parent._context else {}
+ ctx.update(decoder.decode(self.context) if self.context else {})
+ return ctx
+
+ def __check(self, records, on_change=True):
+ config = None
+ for record in records:
+ assert isinstance(record, Model)
+ assert record.__class__.__name__ == self.model_name
+ if self.parent:
+ assert record._config == self.parent._config
+ elif self:
+ assert record._config == self[0]._config
+ elif config:
+ assert record._config == config
+ else:
+ config = record._config
+ if record._group is not self:
+ assert record._group is None
+ record._group = self
+ for record in records:
+ # Set parent field to trigger on_change
+ if (on_change
+ and self.parent
+ and self.parent_name in record._fields):
+ definition = record._fields[self.parent_name]
+ if definition['type'] in ('many2one', 'reference'):
+ setattr(record, self.parent_name, self.parent)
+ self.record_removed.difference_update(records)
+ self.record_deleted.difference_update(records)
+
+ def append(self, record):
+ self.__check([record])
+ res = super(ModelList, self).append(record)
+ self._changed()
+ return res
+ append.__doc__ = list.append.__doc__
+
+ def extend(self, iterable):
+ iterable = list(iterable)
+ self.__check(iterable)
+ res = super(ModelList, self).extend(iterable)
+ self._changed()
+ return res
+ extend.__doc__ = list.extend.__doc__
+
+ def insert(self, index, record):
+ raise NotImplementedError
+ insert.__doc__ = list.insert.__doc__
+
+ def pop(self, index=-1, _changed=True):
+ self.record_removed.add(self[index])
+ self[index]._group = None
+ res = super(ModelList, self).pop(index)
+ if _changed:
+ self._changed()
+ return res
+ pop.__doc__ = list.pop.__doc__
+
+ def remove(self, record, _changed=True):
+ if record.id >= 0:
+ self.record_deleted.add(record)
+ record._group = None
+ res = super(ModelList, self).remove(record)
+ if _changed:
+ self._changed()
+ return res
+ remove.__doc__ = list.remove.__doc__
+
+ def reverse(self):
+ raise NotImplementedError
+ reverse.__doc__ = list.reverse.__doc__
+
+ def sort(self):
+ raise NotImplementedError
+ sort.__doc__ = list.sort.__doc__
+
+ def new(self, **kwargs):
+ 'Adds a new record to the ModelList and returns it'
+ Relation = Model.get(self.model_name, self.parent._config)
+ config = Relation._config
+ with config.reset_context(), config.set_context(self._get_context()):
+ # Set parent for on_change calls from default_get
+ new_record = Relation(_group=self, **kwargs)
+ self.append(new_record)
+ return new_record
+
+ def find(self, condition=None, offset=0, limit=None, order=None):
+ 'Returns records matching condition taking into account list domain'
+ from .pyson import PYSONDecoder
+ decoder = PYSONDecoder(_EvalEnvironment(self.parent))
+ Relation = Model.get(self.model_name, self.parent._config)
+ if condition is None:
+ condition = []
+ field_domain = decoder.decode(self.domain)
+ add_remove_domain = (decoder.decode(self.add_remove)
+ if self.add_remove else [])
+ new_domain = [field_domain, add_remove_domain, condition]
+ context = self._get_context()
+ context.update(decoder.decode(self.search_context))
+ order = order if order else decoder.decode(self.search_order)
+ config = Relation._config
+ with config.reset_context(), config.set_context(context):
+ return Relation.find(new_domain, offset, limit, order)
+
+ def set_sequence(self, field='sequence'):
+ changed = False
+ prev = None
+ for record in self:
+ if prev:
+ index = getattr(prev, field)
+ else:
+ index = None
+ update = False
+ value = getattr(record, field)
+ if value is None:
+ if index:
+ update = True
+ elif prev and record.id >= 0:
+ update = record.id < prev.id
+ elif value == index:
+ if prev and record.id >= 0:
+ update = record.id < prev.id
+ elif value <= (index or 0):
+ update = True
+ if update:
+ if index is None:
+ index = 0
+ index += 1
+ setattr(record, field, index)
+ changed = record
+ prev = record
+ if changed:
+ self._changed()
+
+
+class Model(object):
+ 'Model class for Tryton records'
+ __slots__ = ('__id', '_values', '_changed', '_group', '__context')
+
+ __counter = -1
+ _proxy = None
+ _config = None
+ _fields = None
+
+ def __init__(self, id=None, _default=True, _group=None, **kwargs):
+ super(Model, self).__init__()
+ if id is not None:
+ assert not kwargs
+ self.__id = id if id is not None else Model.__counter
+ if self.__id < 0:
+ Model.__counter -= 1
+ self._values = {} # store the values of fields
+ self._changed = set() # store the changed fields
+ self._group = _group # store the parent group
+ self.__context = self._config.context # store the context
+ if self.id < 0 and _default:
+ self._default_get()
+
+ for field_name, value in kwargs.items():
+ definition = self._fields[field_name]
+ if definition['type'] in ('one2many', 'many2many'):
+ relation = Model.get(definition['relation'], self._config)
+
+ def instantiate(v):
+ if isinstance(v, int):
+ return relation(v)
+ elif isinstance(v, dict):
+ return relation(_default=_default, **v)
+ else:
+ return v
+ value = [instantiate(x) for x in value]
+ getattr(self, field_name).extend(value)
+ else:
+ if definition['type'] == 'many2one':
+ if isinstance(value, int):
+ relation = Model.get(
+ definition['relation'], self._config)
+ value = relation(value)
+ setattr(self, field_name, value)
+ __init__.__doc__ = object.__init__.__doc__
+
+ @property
+ def _parent(self):
+ if self._group is not None:
+ return self._group.parent
+
+ @property
+ def _parent_field_name(self):
+ if self._group is not None:
+ return self._group.parent_field_name
+ return ''
+
+ @property
+ def _parent_name(self):
+ if self._group is not None:
+ return self._group.parent_name
+ return ''
+
+ @property
+ def _context(self):
+ if self._group:
+ context = self._group._get_context()
+ else:
+ context = self.__context
+ return context
+
+ @classmethod
+ def get(cls, name, config=None):
+ 'Get a class for the named Model'
+ if (bytes == str) and isinstance(name, str):
+ name = name.encode('utf-8')
+
+ class Spam(Model, metaclass=MetaModelFactory(name, config=config)()):
+ __slots__ = ()
+ return Spam
+
+ @classmethod
+ def reset(cls, config=None, *names):
+ 'Reset class definition for Models named'
+ config = config or proteus.config.get_config()
+ models_key = 'c%su%s' % (id(config), config.user)
+ if not names:
+ setattr(_MODELS, models_key, {})
+ else:
+ models = getattr(_MODELS, models_key, {})
+ for name in names:
+ del models[name]
+
+ def __str__(self):
+ return '%s,%d' % (self.__class__.__name__, self.id)
+
+ def __repr__(self):
+ if self._config == proteus.config.get_config():
+ return "proteus.Model.get('%s')(%d)" % (self.__class__.__name__,
+ self.id)
+ return "proteus.Model.get('%s', %s)(%d)" % (self.__class__.__name__,
+ repr(self._config), self.id)
+
+ def __eq__(self, other):
+ if isinstance(other, Model):
+ return ((self.__class__.__name__, self.id)
+ == (other.__class__.__name__, other.id))
+ return NotImplemented
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __hash__(self):
+ return hash(self.__class__.__name__) ^ hash(self.id)
+
+ def __int__(self):
+ return self.id
+
+ @property
+ def id(self):
+ 'The unique ID'
+ return self.__id
+
+ @id.setter
+ def id(self, value):
+ assert self.__id < 0
+ self.__id = int(value)
+
+ @classmethod
+ def find(cls, condition=None, offset=0, limit=None, order=None):
+ 'Return records matching condition'
+ if condition is None:
+ condition = []
+ ids = cls._proxy.search(condition, offset, limit, order,
+ cls._config.context)
+ return [cls(id) for id in ids]
+
+ @dualmethod
+ def reload(cls, records):
+ 'Reload record'
+ for record in records:
+ record._values = {}
+ record._changed = set()
+
+ @dualmethod
+ def save(cls, records):
+ 'Save records'
+ if not records:
+ return
+ proxy = records[0]._proxy
+ config = records[0]._config
+ context = records[0]._context.copy()
+ create, write = [], []
+ for record in records:
+ assert proxy == record._proxy
+ assert config == record._config
+ assert context == record._context
+ if record.id < 0:
+ create.append(record)
+ elif record._changed:
+ write.append(record)
+
+ if create:
+ values = [r._get_values() for r in create]
+ ids = proxy.create(values, context)
+ for record, id_ in zip(create, ids):
+ record.id = id_
+ if write:
+ values = []
+ context['_timestamp'] = {}
+ for record in write:
+ values.append([record.id])
+ values.append(record._get_values(fields=record._changed))
+ context['_timestamp'].update(record._get_timestamp())
+ values.append(context)
+ proxy.write(*values)
+ for record in records:
+ record.reload()
+
+ @dualmethod
+ def delete(cls, records):
+ 'Delete records'
+ if not records:
+ return
+ proxy = records[0]._proxy
+ config = records[0]._config
+ context = records[0]._context.copy()
+ timestamp = {}
+ delete = []
+ for record in records:
+ assert proxy == record._proxy
+ assert config == record._config
+ assert context == record._context
+ if record.id >= 0:
+ timestamp.update(record._get_timestamp())
+ delete.append(record.id)
+ context['_timestamp'] = timestamp
+ if delete:
+ proxy.delete(delete, context)
+ cls.reload(records)
+
+ @dualmethod
+ def duplicate(cls, records, default=None):
+ 'Duplicate the record'
+ ids = cls._proxy.copy([r.id for r in records], default,
+ cls._config.context)
+ return [cls(id) for id in ids]
+
+ @dualmethod
+ def click(cls, records, button, change=None):
+ 'Click on button'
+ if not records:
+ return
+
+ proxy = records[0]._proxy
+ config = records[0]._config
+ context = records[0]._context.copy()
+ for record in records:
+ assert proxy == record._proxy
+ assert config == record._config
+ assert context == record._context
+
+ if change is None:
+ cls.save(records)
+ cls.reload(records) # Force reload because save doesn't always
+ return getattr(proxy, button)([r.id for r in records], context)
+ else:
+ record, = records
+ values = record._on_change_args(change)
+ changes = getattr(proxy, button)(values, context)
+ record._set_on_change(changes)
+
+ def _get_values(self, fields=None):
+ 'Return dictionary values'
+ if fields is None:
+ fields = self._values.keys()
+ values = {}
+ for name in fields:
+ if name in ['id', '_timestamp']:
+ continue
+ definition = self._fields[name]
+ if definition.get('readonly') and definition['type'] != 'one2many':
+ continue
+ values[name] = getattr(self, '__%s_value' % name)
+ # Sending an empty X2Many fields breaks ModelFieldAccess.check
+ if (definition['type'] in {'one2many', 'many2many'}
+ and not values[name]):
+ del values[name]
+ return values
+
+ @property
+ def _timestamp(self):
+ 'Get _timestamp'
+ return self._values.get('_timestamp')
+
+ def _get_timestamp(self):
+ 'Return dictionary with timestamps'
+ result = {'%s,%s' % (self.__class__.__name__, self.id):
+ self._timestamp}
+ for field, definition in self._fields.items():
+ if field not in self._values:
+ continue
+ if definition['type'] in ('one2many', 'many2many'):
+ for record in getattr(self, field):
+ result.update(record._get_timestamp())
+ return result
+
+ def _read(self, name):
+ 'Read field'
+ fields = [name]
+ if name in self._values:
+ return
+ loading = self._fields[name]['loading']
+ if loading == 'eager':
+ fields = [x for x, y in self._fields.items()
+ if y['loading'] == 'eager']
+ fields.append('_timestamp')
+ self._values.update(
+ self._proxy.read([self.id], fields, self._context)[0])
+ for field in fields:
+ if (field in self._fields
+ and self._fields[field]['type'] == 'float'
+ and isinstance(self._values[field], Decimal)):
+ # XML-RPC return Decimal for double
+ self._values[field] = float(self._values[field])
+
+ def _default_get(self):
+ 'Set default values'
+ fields = list(self._fields.keys())
+ self._default_set(
+ self._proxy.default_get(fields, False, self._context))
+
+ def _default_set(self, values):
+ fieldnames = []
+ for field, value in values.items():
+ if '.' in field:
+ continue
+ definition = self._fields[field]
+ if definition['type'] in ('one2many', 'many2many'):
+ if value and len(value) and isinstance(value[0], int):
+ self._values[field] = value
+ else:
+ Relation = Model.get(definition['relation'], self._config)
+ self._values[field] = records = ModelList(
+ definition, [], self, field)
+ for vals in (value or []):
+ record = Relation()
+ record._default_set(vals)
+ records.append(record)
+ else:
+ self._values[field] = value
+ fieldnames.append(field)
+ self._on_change(sorted(fieldnames))
+
+ def _get_eval(self):
+ values = dict((x, getattr(self, '__%s_eval' % x))
+ for x in self._fields if x != 'id')
+ values['id'] = self.id
+ return values
+
+ def _get_on_change_values(self, skip=None, fields=None):
+ values = {'id': self.id}
+ if fields:
+ definitions = ((f, self._fields[f]) for f in fields)
+ else:
+ definitions = self._fields.items()
+ for field, definition in definitions:
+ if field == 'id':
+ continue
+ if not fields:
+ if skip and field in skip:
+ continue
+ if (self.id >= 0
+ and (field not in self._values
+ or field not in self._changed)):
+ continue
+ if definition['type'] == 'one2many':
+ values[field] = [x._get_on_change_values(
+ skip={definition.get('relation_field', '')})
+ for x in getattr(self, field)]
+ elif (definition['type'] in ('many2one', 'reference')
+ and self._parent_name == definition['name']
+ and self._parent):
+ values[field] = self._parent._get_on_change_values(
+ skip={self._parent_field_name})
+ if definition['type'] == 'reference':
+ values[field] = (
+ self._parent.__class__.__name__, values[field])
+ else:
+ values[field] = getattr(self, '__%s_eval' % field)
+ return values
+
+ def _on_change_args(self, args):
+ # Ensure arguments has been read
+ for arg in args:
+ record = self
+ for i in arg.split('.'):
+ if i in record._fields:
+ getattr(record, i)
+ elif i == '_parent_' + record._parent_name:
+ getattr(record, record._parent_name)
+ record = record._parent
+
+ res = {}
+ values = _EvalEnvironment(self, 'on_change')
+ for arg in args:
+ scope = values
+ for i in arg.split('.'):
+ if i not in scope:
+ break
+ scope = scope[i]
+ else:
+ res[arg] = scope
+ return res
+
+ def _on_change_set(self, field, value):
+ if (self._fields[field]['type'] in ('one2many', 'many2many')
+ and not isinstance(value, (list, tuple))):
+ if value and value.get('delete'):
+ for record_id in value['delete']:
+ for record in getattr(self, field):
+ if record.id == record_id:
+ getattr(self, field).remove(record, _changed=False)
+ if value and value.get('remove'):
+ for record_id in value['remove']:
+ for i, record in enumerate(getattr(self, field)):
+ if record.id == record_id:
+ getattr(self, field).pop(i, _changed=False)
+ if value and (value.get('add') or value.get('update')):
+ for index, vals in value.get('add', []):
+ group = getattr(self, field)
+ Relation = Model.get(
+ self._fields[field]['relation'], self._config)
+ config = Relation._config
+ id_ = vals.pop('id', None)
+ with config.reset_context(), \
+ config.set_context(self._context):
+ record = Relation(id=id_, _group=group, _default=False)
+ try:
+ idx = group.index(record)
+ except ValueError:
+ # append without signal
+ if index == -1:
+ list.append(group, record)
+ else:
+ list.insert(group, index, record)
+ else:
+ record = group[idx]
+ group.record_removed.discard(record)
+ group.record_deleted.discard(record)
+ record._set_on_change(vals)
+ for vals in value.get('update', []):
+ if 'id' not in vals:
+ continue
+ for record in getattr(self, field):
+ if record.id == vals['id']:
+ record._set_on_change(vals)
+ elif (self._fields[field]['type'] in ('one2many', 'many2many')
+ and len(value) and not isinstance(value[0], int)):
+ self._values[field] = []
+ for vals in value:
+ Relation = Model.get(
+ self._fields[field]['relation'], self._config)
+ config = Relation._config
+ records = getattr(self, field)
+ with config.reset_context(), \
+ config.set_context(records._get_context()):
+ record = Relation(_default=False, **vals)
+ records.append(record)
+ else:
+ self._values[field] = value
+ self._changed.add(field)
+
+ def _set_on_change(self, values):
+ later = {}
+ for field, value in values.items():
+ if field not in self._fields:
+ continue
+ if self._fields[field]['type'] in ('one2many', 'many2many'):
+ later[field] = value
+ continue
+ self._on_change_set(field, value)
+ for field, value in later.items():
+ self._on_change_set(field, value)
+
+ def _on_change(self, names):
+ 'Call on_change for field'
+ # Import locally to not break installation
+ from proteus.pyson import PYSONDecoder
+
+ values = {}
+ for name in names:
+ definition = self._fields[name]
+ on_change = definition.get('on_change')
+ if not on_change:
+ continue
+ if isinstance(on_change, str):
+ definition['on_change'] = on_change = PYSONDecoder().decode(
+ on_change)
+ values.update(self._on_change_args(on_change))
+ if values:
+ context = self._context
+ changes = getattr(self._proxy, 'on_change')(values, names, context)
+ for change in changes:
+ self._set_on_change(change)
+
+ values = {}
+ fieldnames = set(names)
+ to_change = set()
+ later = set()
+ for field, definition in self._fields.items():
+ on_change_with = definition.get('on_change_with')
+ if not on_change_with:
+ continue
+ if not fieldnames & set(on_change_with):
+ continue
+ if to_change & set(on_change_with):
+ later.add(field)
+ continue
+ to_change.add(field)
+ values.update(self._on_change_args(on_change_with))
+ if to_change:
+ context = self._context
+ changes = getattr(self._proxy, 'on_change_with')(values,
+ list(to_change), context)
+ self._set_on_change(changes)
+ for field in later:
+ on_change_with = self._fields[field]['on_change_with']
+ values = self._on_change_args(on_change_with)
+ context = self._context
+ result = getattr(self._proxy, 'on_change_with_%s' % field)(values,
+ context)
+ self._on_change_set(field, result)
+
+ if self._parent:
+ self._parent._changed.add(self._parent_field_name)
+ self._parent._on_change([self._parent_field_name])
+
+
+class Wizard(object):
+ 'Wizard class for Tryton wizards'
+ __slots__ = ('name', 'form', 'form_state', 'actions', '_config',
+ '_context', '_proxy', 'session_id', 'start_state', 'end_state',
+ 'states', 'state', 'models', 'action')
+
+ def __init__(self, name, models=None, action=None, config=None,
+ context=None):
+ if models:
+ assert len(set(type(x) for x in models)) == 1
+ super(Wizard, self).__init__()
+ self.name = name
+ self.form = None
+ self.form_state = None
+ self.actions = []
+ self._config = config or proteus.config.get_config()
+ self._context = self._config.context
+ if context:
+ self._context.update(context)
+ self._proxy = self._config.get_proxy(name, type='wizard')
+ result = self._proxy.create(self._context)
+ self.session_id, self.start_state, self.end_state = result
+ self.states = [self.start_state]
+ self.models = models
+ self.action = action
+ self.execute(self.start_state)
+
+ def execute(self, state):
+ assert state in self.states
+
+ self.state = state
+ while self.state != self.end_state:
+ ctx = self._context.copy()
+ if self.models:
+ ctx['active_id'] = self.models[0].id
+ ctx['active_ids'] = [model.id for model in self.models]
+ ctx['active_model'] = self.models[0].__class__.__name__
+ else:
+ ctx['active_id'] = None
+ ctx['active_ids'] = None
+ ctx['active_model'] = None
+ if self.action:
+ ctx['action_id'] = self.action['id']
+ else:
+ ctx['action_id'] = None
+
+ if self.form:
+ data = {self.form_state: self.form._get_on_change_values()}
+ else:
+ data = {}
+
+ result = self._proxy.execute(self.session_id, data, self.state,
+ ctx)
+
+ if 'view' in result:
+ view = result['view']
+ self.form = Model.get(
+ view['fields_view']['model'], self._config)()
+ self.form._default_set(view['defaults'])
+ self.states = [b['state'] for b in view['buttons']]
+ self.form_state = view['state']
+ else:
+ self.state = self.end_state
+
+ self.actions = []
+ for action in result.get('actions', []):
+ proteus_action = _convert_action(*action,
+ context=self._context)
+ if proteus_action:
+ self.actions.append(proteus_action)
+
+ if 'view' in result:
+ return
+
+ if self.state == self.end_state:
+ self._proxy.delete(self.session_id, self._context)
+ if self.models:
+ for record in self.models:
+ record.reload()
+
+
+class Report(object):
+ 'Report class for Tryton reports'
+ __slots__ = ('name', '_config', '_context', '_proxy')
+
+ def __init__(self, name, config=None, context=None):
+ super(Report, self).__init__()
+ self.name = name
+ self._config = config or proteus.config.get_config()
+ self._context = self._config.context
+ if context:
+ self._context.update(context)
+ self._proxy = self._config.get_proxy(name, type='report')
+
+ def execute(self, models=None, data=None):
+ if models:
+ ids = [m.id for m in models]
+ elif data:
+ ids = data.get('ids', [])
+ else:
+ ids = []
+ if data is None:
+ data = {
+ 'id': ids[0] if ids else None,
+ 'ids': ids,
+ }
+ if models:
+ data['model'] = models[0].__class__.__name__
+ return self._proxy.execute(ids, data, self._context)
+
+
+def _convert_action(action, data=None, context=None, config=None):
+ if config is None:
+ config = proteus.config.get_config()
+ if data is None:
+ data = {}
+ else:
+ data = data.copy()
+
+ if 'type' not in (action or {}):
+ return None
+
+ data['action_id'] = action['id']
+ if action['type'] == 'ir.action.act_window':
+ from .pyson import PYSONDecoder
+
+ action.setdefault('pyson_domain', '[]')
+ ctx = {
+ 'active_model': data.get('model'),
+ 'active_id': data.get('id'),
+ 'active_ids': data.get('ids', []),
+ }
+ ctx.update(config.context)
+ ctx['_user'] = config.user
+ decoder = PYSONDecoder(ctx)
+ action_ctx = decoder.decode(action.get('pyson_context') or '{}')
+ ctx.update(action_ctx)
+ ctx.update(context)
+ action_ctx.update(context)
+ if 'date_format' not in action_ctx:
+ action_ctx['date_format'] = config.context.get(
+ 'locale', {}).get('date', '%x')
+
+ ctx['context'] = ctx
+ decoder = PYSONDecoder(ctx)
+ domain = decoder.decode(action['pyson_domain'])
+
+ res_model = action.get('res_model', data.get('res_model'))
+ res_id = action.get('res_id', data.get('res_id'))
+ Model_ = Model.get(res_model, config)
+ config = Model_._config
+ with config.reset_context(), config.set_context(action_ctx):
+ if res_id is None:
+ return Model_.find(domain)
+ elif isinstance(res_id, int):
+ return [Model_(res_id)]
+ else:
+ return [Model_(id_) for id_ in res_id]
+ elif action['type'] == 'ir.action.wizard':
+ kwargs = {
+ 'action': action,
+ 'config': config,
+ 'context': context,
+ }
+ if 'model' in data:
+ Model_ = Model.get(data['model'], config)
+ config = Model_._config
+ with config.reset_context(), config.set_context(context):
+ kwargs['models'] = [Model_(id_) for id_ in data.get('ids', [])]
+ return Wizard(action['wiz_name'], **kwargs)
+ elif action['type'] == 'ir.action.report':
+ ActionReport = Report(action['report_name'], context=context)
+ return ActionReport.execute(data=data)
+ elif action['type'] == 'ir.action.url':
+ return action.get('url')
diff --git a/proteus/proteus/config.py b/proteus/proteus/config.py
new file mode 100644
index 0000000000..5373be56ba
--- /dev/null
+++ b/proteus/proteus/config.py
@@ -0,0 +1,378 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+"""
+Configuration functions for the proteus package for Tryton.
+"""
+
+import datetime
+import os
+import threading
+import urllib.parse
+import xmlrpc.client
+from decimal import Decimal
+
+import defusedxml.xmlrpc
+
+__all__ = ['set_trytond', 'set_xmlrpc', 'get_config']
+
+defusedxml.xmlrpc.monkey_patch()
+
+
+def dump_decimal(self, value, write):
+ value = {'__class__': 'Decimal',
+ 'decimal': str(value),
+ }
+ self.dump_struct(value, write)
+
+
+def dump_date(self, value, write):
+ value = {'__class__': 'date',
+ 'year': value.year,
+ 'month': value.month,
+ 'day': value.day,
+ }
+ self.dump_struct(value, write)
+
+
+def dump_time(self, value, write):
+ value = {'__class__': 'time',
+ 'hour': value.hour,
+ 'minute': value.minute,
+ 'second': value.second,
+ 'microsecond': value.microsecond,
+ }
+ self.dump_struct(value, write)
+
+
+def dump_timedelta(self, value, write):
+ value = {'__class__': 'timedelta',
+ 'seconds': value.total_seconds(),
+ }
+ self.dump_struct(value, write)
+
+
+def dump_long(self, value, write):
+ try:
+ self.dump_long(value, write)
+ except OverflowError:
+ write('')
+ write(str(int(value)))
+ write('\n')
+
+
+xmlrpc.client.Marshaller.dispatch[Decimal] = dump_decimal
+xmlrpc.client.Marshaller.dispatch[datetime.date] = dump_date
+xmlrpc.client.Marshaller.dispatch[datetime.time] = dump_time
+xmlrpc.client.Marshaller.dispatch[datetime.timedelta] = dump_timedelta
+xmlrpc.client.Marshaller.dispatch[int] = dump_long
+
+
+def dump_struct(self, value, write, escape=xmlrpc.client.escape):
+ converted_value = {}
+ for k, v in value.items():
+ if isinstance(k, int):
+ k = str(k)
+ elif isinstance(k, float):
+ k = repr(k)
+ converted_value[k] = v
+ return self.dump_struct(converted_value, write, escape=escape)
+
+
+xmlrpc.client.Marshaller.dispatch[dict] = dump_struct
+
+
+class XMLRPCDecoder(object):
+
+ decoders = {}
+
+ @classmethod
+ def register(cls, klass, decoder):
+ assert klass not in cls.decoders
+ cls.decoders[klass] = decoder
+
+ def __call__(self, dct):
+ if dct.get('__class__') in self.decoders:
+ return self.decoders[dct['__class__']](dct)
+ return dct
+
+
+XMLRPCDecoder.register('date',
+ lambda dct: datetime.date(dct['year'], dct['month'], dct['day']))
+XMLRPCDecoder.register('time',
+ lambda dct: datetime.time(dct['hour'], dct['minute'], dct['second'],
+ dct['microsecond']))
+XMLRPCDecoder.register('timedelta',
+ lambda dct: datetime.timedelta(seconds=dct['seconds']))
+XMLRPCDecoder.register('Decimal', lambda dct: Decimal(dct['decimal']))
+
+
+def end_struct(self, data):
+ mark = self._marks.pop()
+ # map structs to Python dictionaries
+ dct = {}
+ items = self._stack[mark:]
+ for i in range(0, len(items), 2):
+ dct[items[i]] = items[i + 1]
+ dct = XMLRPCDecoder()(dct)
+ self._stack[mark:] = [dct]
+ self._value = 0
+
+
+xmlrpc.client.Unmarshaller.dispatch['struct'] = end_struct
+
+_CONFIG = threading.local()
+_CONFIG.current = None
+
+
+class ContextManager(object):
+ 'Context Manager for the tryton context'
+
+ def __init__(self, config):
+ self.config = config
+ self.context = config.context
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.config._context = self.context
+
+
+class Config(object):
+ 'Config interface'
+
+ def __init__(self):
+ super(Config, self).__init__()
+ self._context = {}
+
+ @property
+ def context(self):
+ return self._context.copy()
+
+ def set_context(self, context=None, **kwargs):
+ ctx_manager = ContextManager(self)
+
+ if context is None:
+ context = {}
+ self._context = self.context
+ self._context.update(context)
+ self._context.update(kwargs)
+ return ctx_manager
+
+ def reset_context(self):
+ ctx_manager = ContextManager(self)
+ self._context = {}
+ return ctx_manager
+
+ def get_proxy(self, name):
+ raise NotImplementedError
+
+ def get_proxy_methods(self, name):
+ raise NotImplementedError
+
+
+class _TrytondMethod(object):
+
+ def __init__(self, name, model, config):
+ super(_TrytondMethod, self).__init__()
+ self._name = name
+ self._object = model
+ self._config = config
+
+ def __call__(self, *args, **kwargs):
+ from trytond.rpc import RPC
+ from trytond.tools import is_instance_method
+ from trytond.transaction import Transaction
+ from trytond.worker import run_task
+
+ if self._name in self._object.__rpc__:
+ rpc = self._object.__rpc__[self._name]
+ elif self._name in getattr(self._object, '_buttons', {}):
+ rpc = RPC(readonly=False, instantiate=0)
+ else:
+ raise TypeError('%s is not callable' % self._name)
+
+ with Transaction().start(self._config.database_name,
+ self._config.user, readonly=rpc.readonly) as transaction:
+ args, kwargs, transaction.context, transaction.timestamp = \
+ rpc.convert(self._object, *args, **kwargs)
+ meth = getattr(self._object, self._name)
+ if (rpc.instantiate is None
+ or not is_instance_method(self._object, self._name)):
+ result = rpc.result(meth(*args, **kwargs))
+ else:
+ assert rpc.instantiate == 0
+ inst = args.pop(0)
+ if hasattr(inst, self._name):
+ result = rpc.result(meth(inst, *args, **kwargs))
+ else:
+ result = [rpc.result(meth(i, *args, **kwargs))
+ for i in inst]
+ transaction.commit()
+ while transaction.tasks:
+ task_id = transaction.tasks.pop()
+ run_task(self._config.database_name, task_id)
+ return result
+
+
+class TrytondProxy(object):
+ 'Proxy for function call for trytond'
+
+ def __init__(self, name, config, type='model'):
+ super(TrytondProxy, self).__init__()
+ self._config = config
+ self._object = config.pool.get(name, type=type)
+ __init__.__doc__ = object.__init__.__doc__
+
+ def __getattr__(self, name):
+ 'Return attribute value'
+ return _TrytondMethod(name, self._object, self._config)
+
+
+class TrytondConfig(Config):
+ 'Configuration for trytond'
+
+ def __init__(self, database=None, user='admin', config_file=None):
+ super(TrytondConfig, self).__init__()
+ if not database:
+ database = os.environ.get('TRYTOND_DATABASE_URI')
+ elif (os.environ.get('TRYTOND_DATABASE_URI')
+ and not urllib.parse.urlparse(database).scheme):
+ url = urllib.parse.urlparse(os.environ['TRYTOND_DATABASE_URI'])
+ os.environ['TRYTOND_DATABASE_URI'] = urllib.parse.urlunparse(
+ url._replace(path=database))
+ else:
+ os.environ['TRYTOND_DATABASE_URI'] = database
+ if not config_file:
+ config_file = os.environ.get('TRYTOND_CONFIG')
+ from trytond.config import config
+ config.update_etc(config_file)
+ from trytond.pool import Pool
+ from trytond.transaction import Transaction
+ self.database = database
+ database_name = None
+ if database:
+ uri = urllib.parse.urlparse(database)
+ database_name = uri.path.strip('/')
+ if not database_name:
+ database_name = os.environ['DB_NAME']
+ self.database_name = database_name
+ self._user = user
+ self.config_file = config_file
+
+ Pool.start()
+ self.pool = Pool(database_name)
+ self.pool.init()
+
+ with Transaction().start(self.database_name, 0) as transaction:
+ User = self.pool.get('res.user')
+ transaction.context = self.context
+ with transaction.set_context(active_test=False):
+ self.user = User.search([
+ ('login', '=', user),
+ ], limit=1)[0].id
+ with transaction.set_user(self.user):
+ self._context = User.get_preferences(context_only=True)
+ __init__.__doc__ = object.__init__.__doc__
+
+ def __repr__(self):
+ return ("proteus.config.TrytondConfig"
+ "(%s, %s, config_file=%s)"
+ % (repr(self.database), repr(self._user), repr(self.config_file)))
+ __repr__.__doc__ = object.__repr__.__doc__
+
+ def __eq__(self, other):
+ if not isinstance(other, TrytondConfig):
+ raise NotImplementedError
+ return (self.database_name == other.database_name
+ and self._user == other._user
+ and self.database == other.database
+ and self.config_file == other.config_file)
+
+ def __hash__(self):
+ return hash((self.database_name, self._user,
+ self.database, self.config_file))
+
+ def get_proxy(self, name, type='model'):
+ 'Return Proxy class'
+ return TrytondProxy(name, self, type=type)
+
+ def get_proxy_methods(self, name, type='model'):
+ 'Return list of methods'
+ proxy = self.get_proxy(name, type=type)
+ methods = [x for x in proxy._object.__rpc__]
+ if hasattr(proxy._object, '_buttons'):
+ methods += [x for x in proxy._object._buttons]
+ return methods
+
+
+def set_trytond(database=None, user='admin',
+ config_file=None):
+ 'Set trytond package as backend'
+ _CONFIG.current = TrytondConfig(database, user, config_file=config_file)
+ return _CONFIG.current
+
+
+class XmlrpcProxy(object):
+ 'Proxy for function call for XML-RPC'
+
+ def __init__(self, name, config, type='model'):
+ super(XmlrpcProxy, self).__init__()
+ self._config = config
+ self._object = getattr(config.server, '%s.%s' % (type, name))
+ __init__.__doc__ = object.__init__.__doc__
+
+ def __getattr__(self, name):
+ 'Return attribute value'
+ return getattr(self._object, name)
+
+
+class XmlrpcConfig(Config):
+ 'Configuration for XML-RPC'
+
+ def __init__(self, url, **kwargs):
+ super(XmlrpcConfig, self).__init__()
+ self.url = url
+ self.server = xmlrpc.client.ServerProxy(
+ url, allow_none=True, use_builtin_types=True, **kwargs)
+ # TODO add user
+ self.user = None
+ self._context = self.server.model.res.user.get_preferences(True, {})
+ __init__.__doc__ = object.__init__.__doc__
+
+ def __repr__(self):
+ return "proteus.config.XmlrpcConfig(%s)" % repr(self.url)
+ __repr__.__doc__ = object.__repr__.__doc__
+
+ def __eq__(self, other):
+ if not isinstance(other, XmlrpcConfig):
+ raise NotImplementedError
+ return self.url == other.url
+
+ def __hash__(self):
+ return hash(self.url)
+
+ def get_proxy(self, name, type='model'):
+ 'Return Proxy class'
+ return XmlrpcProxy(name, self, type=type)
+
+ def get_proxy_methods(self, name, type='model'):
+ 'Return list of methods'
+ object_ = '%s.%s' % (type, name)
+ return [x[len(object_) + 1:]
+ for x in self.server.system.listMethods()
+ if x.startswith(object_)
+ and '.' not in x[len(object_) + 1:]]
+
+
+def set_xmlrpc(url, **kwargs):
+ '''
+ Set XML-RPC as backend.
+ It pass the keyword arguments received to xmlrpclib.ServerProxy()
+ '''
+ _CONFIG.current = XmlrpcConfig(url, **kwargs)
+ return _CONFIG.current
+
+
+def get_config():
+ return _CONFIG.current
diff --git a/proteus/proteus/pyson.py b/proteus/proteus/pyson.py
new file mode 100644
index 0000000000..9dc06dc11d
--- /dev/null
+++ b/proteus/proteus/pyson.py
@@ -0,0 +1,753 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import json
+import datetime
+from decimal import Decimal
+from dateutil.relativedelta import relativedelta
+from functools import reduce
+
+
+class PYSON(object):
+
+ def pyson(self):
+ raise NotImplementedError
+
+ def types(self):
+ raise NotImplementedError
+
+ @staticmethod
+ def eval(dct, context):
+ raise NotImplementedError
+
+ def __invert__(self):
+ if self.types() != {bool}:
+ return Not(Bool(self))
+ else:
+ return Not(self)
+
+ def __and__(self, other):
+ if (isinstance(other, PYSON)
+ and other.types() != {bool}):
+ other = Bool(other)
+ if (isinstance(self, And)
+ and not isinstance(self, Or)):
+ return And(*self._statements, other)
+ if self.types() != {bool}:
+ return And(Bool(self), other)
+ else:
+ return And(self, other)
+
+ __rand__ = __and__
+
+ def __or__(self, other):
+ if (isinstance(other, PYSON)
+ and other.types() != {bool}):
+ other = Bool(other)
+ if isinstance(self, Or):
+ return Or(*self._statements, other)
+ if self.types() != {bool}:
+ return Or(Bool(self), other)
+ else:
+ return Or(self, other)
+
+ __ror__ = __or__
+
+ def __eq__(self, other):
+ return Equal(self, other)
+
+ def __ne__(self, other):
+ return Not(Equal(self, other))
+
+ def __gt__(self, other):
+ return Greater(self, other)
+
+ def __ge__(self, other):
+ return Greater(self, other, True)
+
+ def __lt__(self, other):
+ return Less(self, other)
+
+ def __le__(self, other):
+ return Less(self, other, True)
+
+ def get(self, k, d=''):
+ return Get(self, k, d)
+
+ def in_(self, obj):
+ return In(self, obj)
+
+ def contains(self, k):
+ return In(k, self)
+
+ def __repr__(self):
+ klass = self.__class__.__name__
+ return '%s(%s)' % (klass, ', '.join(map(repr, self.__repr_params__)))
+
+ @property
+ def __repr_params__(self):
+ return NotImplementedError
+
+
+class PYSONEncoder(json.JSONEncoder):
+
+ def default(self, obj):
+ if isinstance(obj, PYSON):
+ return obj.pyson()
+ elif isinstance(obj, datetime.date):
+ if isinstance(obj, datetime.datetime):
+ return DateTime(obj.year, obj.month, obj.day,
+ obj.hour, obj.minute, obj.second, obj.microsecond
+ ).pyson()
+ else:
+ return Date(obj.year, obj.month, obj.day).pyson()
+ elif isinstance(obj, Decimal):
+ return float(obj)
+ return super(PYSONEncoder, self).default(obj)
+
+
+class PYSONDecoder(json.JSONDecoder):
+
+ def __init__(self, context=None, noeval=False):
+ self.__context = context or {}
+ self.noeval = noeval
+ super(PYSONDecoder, self).__init__(object_hook=self._object_hook)
+
+ def _object_hook(self, dct):
+ if '__class__' in dct:
+ klass = CONTEXT.get(dct['__class__'])
+ if klass:
+ if not self.noeval:
+ return klass.eval(dct, self.__context)
+ else:
+ dct = dct.copy()
+ del dct['__class__']
+ return klass(**dct)
+ return dct
+
+
+class Eval(PYSON):
+
+ def __init__(self, v, d=''):
+ super(Eval, self).__init__()
+ self._value = v
+ self._default = d
+
+ @property
+ def __repr_params__(self):
+ return self._value, self._default
+
+ def pyson(self):
+ return {
+ '__class__': 'Eval',
+ 'v': self._value,
+ 'd': self._default,
+ }
+
+ def types(self):
+ if isinstance(self._default, PYSON):
+ return self._default.types()
+ else:
+ return {type(self._default)}
+
+ @staticmethod
+ def eval(dct, context):
+ if '.' in dct['v'] and dct['v'] not in context:
+ base, name = dct['v'].split('.', 1)
+ return Eval.eval({
+ 'v': name,
+ 'd': dct['d'],
+ }, context.get(base) or {})
+ return context.get(dct['v'], dct['d'])
+
+
+class Not(PYSON):
+
+ def __init__(self, v):
+ super(Not, self).__init__()
+ if isinstance(v, PYSON):
+ if v.types() != {bool}:
+ v = Bool(v)
+ elif not isinstance(v, bool):
+ v = bool(v)
+ self._value = v
+
+ @property
+ def __repr_params__(self):
+ return (self._value,)
+
+ def pyson(self):
+ return {
+ '__class__': 'Not',
+ 'v': self._value,
+ }
+
+ def types(self):
+ return {bool}
+
+ @staticmethod
+ def eval(dct, context):
+ return not dct['v']
+
+
+class Bool(PYSON):
+
+ def __init__(self, v):
+ super(Bool, self).__init__()
+ self._value = v
+
+ @property
+ def __repr_params__(self):
+ return (self._value,)
+
+ def pyson(self):
+ return {
+ '__class__': 'Bool',
+ 'v': self._value,
+ }
+
+ def types(self):
+ return {bool}
+
+ @staticmethod
+ def eval(dct, context):
+ return bool(dct['v'])
+
+
+class And(PYSON):
+
+ def __init__(self, *statements, **kwargs):
+ super(And, self).__init__()
+ statements = list(statements) + kwargs.get('s', [])
+ for i, statement in enumerate(list(statements)):
+ if isinstance(statement, PYSON):
+ if statement.types() != {bool}:
+ statements[i] = Bool(statement)
+ elif not isinstance(statement, bool):
+ statements[i] = bool(statement)
+ assert len(statements) >= 2, 'must have at least 2 statements'
+ self._statements = statements
+
+ @property
+ def __repr_params__(self):
+ return tuple(self._statements)
+
+ def pyson(self):
+ return {
+ '__class__': 'And',
+ 's': self._statements,
+ }
+
+ def types(self):
+ return {bool}
+
+ @staticmethod
+ def eval(dct, context):
+ return bool(reduce(lambda x, y: x and y, dct['s']))
+
+
+class Or(And):
+
+ def pyson(self):
+ res = super(Or, self).pyson()
+ res['__class__'] = 'Or'
+ return res
+
+ @staticmethod
+ def eval(dct, context):
+ return bool(reduce(lambda x, y: x or y, dct['s']))
+
+
+class Equal(PYSON):
+
+ def __init__(self, s1, s2):
+ statement1, statement2 = s1, s2
+ super(Equal, self).__init__()
+ if isinstance(statement1, PYSON):
+ types1 = statement1.types()
+ else:
+ types1 = {type(s1)}
+ if isinstance(statement2, PYSON):
+ types2 = statement2.types()
+ else:
+ types2 = {type(s2)}
+ assert types1 == types2, 'statements must have the same type'
+ self._statement1 = statement1
+ self._statement2 = statement2
+
+ @property
+ def __repr_params__(self):
+ return (self._statement1, self._statement2)
+
+ def pyson(self):
+ return {
+ '__class__': 'Equal',
+ 's1': self._statement1,
+ 's2': self._statement2,
+ }
+
+ def types(self):
+ return {bool}
+
+ @staticmethod
+ def eval(dct, context):
+ return dct['s1'] == dct['s2']
+
+
+class Greater(PYSON):
+
+ def __init__(self, s1, s2, e=False):
+ statement1, statement2, equal = s1, s2, e
+ super(Greater, self).__init__()
+ for i in (statement1, statement2):
+ if isinstance(i, PYSON):
+ assert i.types().issubset({
+ int, float, type(None),
+ datetime.datetime, datetime.date}), \
+ 'statement must be an integer, float, date or datetime'
+ else:
+ assert isinstance(i, (
+ int, float, type(None),
+ datetime.datetime, datetime.date)), \
+ 'statement must be an integer, float, date or datetime'
+ if isinstance(equal, PYSON):
+ if equal.types() != {bool}:
+ equal = Bool(equal)
+ elif not isinstance(equal, bool):
+ equal = bool(equal)
+ self._statement1 = statement1
+ self._statement2 = statement2
+ self._equal = equal
+
+ @property
+ def __repr_params__(self):
+ return (self._statement1, self._statement2, self._equal)
+
+ def pyson(self):
+ return {
+ '__class__': 'Greater',
+ 's1': self._statement1,
+ 's2': self._statement2,
+ 'e': self._equal,
+ }
+
+ def types(self):
+ return {bool}
+
+ @staticmethod
+ def _convert(dct):
+ for i in ('s1', 's2'):
+ if dct[i] is None:
+ dct[i] = 0.0
+ if not isinstance(dct[i], (int, float)):
+ dct = dct.copy()
+ stmt = dct[i]
+ if isinstance(stmt, datetime.datetime):
+ stmt = stmt.timestamp()
+ elif isinstance(stmt, datetime.date):
+ time = datetime.time(0, 0)
+ stmt = datetime.datetime.combine(stmt, time).timestamp()
+ dct[i] = float(stmt)
+ return dct
+
+ @staticmethod
+ def eval(dct, context):
+ if dct['s1'] is None or dct['s2'] is None:
+ return False
+ dct = Greater._convert(dct)
+ if dct['e']:
+ return dct['s1'] >= dct['s2']
+ else:
+ return dct['s1'] > dct['s2']
+
+
+class Less(Greater):
+
+ def pyson(self):
+ res = super(Less, self).pyson()
+ res['__class__'] = 'Less'
+ return res
+
+ @staticmethod
+ def eval(dct, context):
+ if dct['s1'] is None or dct['s2'] is None:
+ return False
+ dct = Less._convert(dct)
+ if dct['e']:
+ return dct['s1'] <= dct['s2']
+ else:
+ return dct['s1'] < dct['s2']
+
+
+class If(PYSON):
+
+ def __init__(self, c, t, e=None):
+ condition, then_statement, else_statement = c, t, e
+ super(If, self).__init__()
+ if isinstance(condition, PYSON):
+ if condition.types() != {bool}:
+ condition = Bool(condition)
+ elif not isinstance(condition, bool):
+ condition = bool(condition)
+ if isinstance(then_statement, PYSON):
+ then_types = then_statement.types()
+ else:
+ then_types = {type(then_statement)}
+ if isinstance(else_statement, PYSON):
+ else_types = else_statement.types()
+ else:
+ else_types = {type(else_statement)}
+ assert then_types == else_types, \
+ 'then and else statements must be the same type'
+ self._condition = condition
+ self._then_statement = then_statement
+ self._else_statement = else_statement
+
+ @property
+ def __repr_params__(self):
+ return (self._condition, self._then_statement, self._else_statement)
+
+ def pyson(self):
+ return {
+ '__class__': 'If',
+ 'c': self._condition,
+ 't': self._then_statement,
+ 'e': self._else_statement,
+ }
+
+ def types(self):
+ if isinstance(self._then_statement, PYSON):
+ return self._then_statement.types()
+ else:
+ return {type(self._then_statement)}
+
+ @staticmethod
+ def eval(dct, context):
+ if dct['c']:
+ return dct['t']
+ else:
+ return dct['e']
+
+
+class Get(PYSON):
+
+ def __init__(self, v, k, d=''):
+ obj, key, default = v, k, d
+ super(Get, self).__init__()
+ if isinstance(obj, PYSON):
+ assert obj.types() == {dict}, 'obj must be a dict'
+ else:
+ assert isinstance(obj, dict), 'obj must be a dict'
+ self._obj = obj
+ if isinstance(key, PYSON):
+ assert key.types() == {str}, 'key must be a string'
+ else:
+ assert isinstance(key, str), 'key must be a string'
+ self._key = key
+ self._default = default
+
+ @property
+ def __repr_params__(self):
+ return (self._obj, self._key, self._default)
+
+ def pyson(self):
+ return {
+ '__class__': 'Get',
+ 'v': self._obj,
+ 'k': self._key,
+ 'd': self._default,
+ }
+
+ def types(self):
+ if isinstance(self._default, PYSON):
+ return self._default.types()
+ else:
+ return {type(self._default)}
+
+ @staticmethod
+ def eval(dct, context):
+ return dct['v'].get(dct['k'], dct['d'])
+
+
+class In(PYSON):
+
+ def __init__(self, k, v):
+ key, obj = k, v
+ super(In, self).__init__()
+ if isinstance(key, PYSON):
+ assert key.types().issubset({str, int}), \
+ 'key must be a string or an integer or a long'
+ else:
+ assert isinstance(key, (str, int)), \
+ 'key must be a string or an integer or a long'
+ if isinstance(obj, PYSON):
+ assert obj.types().issubset({dict, list}), \
+ 'obj must be a dict or a list'
+ if obj.types() == {dict}:
+ assert isinstance(key, str), 'key must be a string'
+ else:
+ assert isinstance(obj, (dict, list))
+ if isinstance(obj, dict):
+ assert isinstance(key, str), 'key must be a string'
+ self._key = key
+ self._obj = obj
+
+ @property
+ def __repr_params__(self):
+ return (self._key, self._obj)
+
+ def pyson(self):
+ return {
+ '__class__': 'In',
+ 'k': self._key,
+ 'v': self._obj,
+ }
+
+ def types(self):
+ return {bool}
+
+ @staticmethod
+ def eval(dct, context):
+ if dct['v']:
+ return dct['k'] in dct['v']
+ else:
+ return False
+
+
+class Date(PYSON):
+
+ def __init__(self, year=None, month=None, day=None,
+ delta_years=0, delta_months=0, delta_days=0, start=None, **kwargs):
+ year = kwargs.get('y', year)
+ month = kwargs.get('M', month)
+ day = kwargs.get('d', day)
+ delta_years = kwargs.get('dy', delta_years)
+ delta_months = kwargs.get('dM', delta_months)
+ delta_days = kwargs.get('dd', delta_days)
+ super(Date, self).__init__()
+ for i in (year, month, day, delta_years, delta_months, delta_days):
+ if isinstance(i, PYSON):
+ assert i.types().issubset({int, type(None)}), \
+ '%s must be an integer or None' % (i,)
+ else:
+ assert isinstance(i, (int, type(None))), \
+ '%s must be an integer or None' % (i,)
+ self._year = year
+ self._month = month
+ self._day = day
+ self._delta_years = delta_years
+ self._delta_months = delta_months
+ self._delta_days = delta_days
+ self._start = start
+
+ @property
+ def __repr_params__(self):
+ return (self._year, self._month, self._day,
+ self._delta_years, self._delta_months, self._delta_days,
+ self._start)
+
+ def pyson(self):
+ return {
+ '__class__': 'Date',
+ 'y': self._year,
+ 'M': self._month,
+ 'd': self._day,
+ 'dy': self._delta_years,
+ 'dM': self._delta_months,
+ 'dd': self._delta_days,
+ 'start': self._start,
+ }
+
+ def types(self):
+ return {datetime.date}
+
+ @staticmethod
+ def eval(dct, context):
+ today = dct.get('start')
+ if isinstance(today, datetime.datetime):
+ today = today.date()
+ if not isinstance(today, datetime.date):
+ today = datetime.date.today()
+ return today + relativedelta(
+ year=dct['y'],
+ month=dct['M'],
+ day=dct['d'],
+ years=dct['dy'],
+ months=dct['dM'],
+ days=dct['dd'],
+ )
+
+
+class DateTime(Date):
+
+ def __init__(self, year=None, month=None, day=None,
+ hour=None, minute=None, second=None, microsecond=None,
+ delta_years=0, delta_months=0, delta_days=0,
+ delta_hours=0, delta_minutes=0, delta_seconds=0,
+ delta_microseconds=0, start=None, **kwargs):
+ hour = kwargs.get('h', hour)
+ minute = kwargs.get('m', minute)
+ second = kwargs.get('s', second)
+ microsecond = kwargs.get('ms', microsecond)
+ delta_hours = kwargs.get('dh', delta_hours)
+ delta_minutes = kwargs.get('dm', delta_minutes)
+ delta_seconds = kwargs.get('ds', delta_seconds)
+ delta_microseconds = kwargs.get('dms', delta_microseconds)
+ super(DateTime, self).__init__(year=year, month=month, day=day,
+ delta_years=delta_years, delta_months=delta_months,
+ delta_days=delta_days, start=start, **kwargs)
+ for i in (hour, minute, second, microsecond,
+ delta_hours, delta_minutes, delta_seconds, delta_microseconds):
+ if isinstance(i, PYSON):
+ assert i.types() == {int, type(None)}, \
+ '%s must be an integer or None' % (i,)
+ else:
+ assert isinstance(i, (int, type(None))), \
+ '%s must be an integer or None' % (i,)
+ self._hour = hour
+ self._minute = minute
+ self._second = second
+ self._microsecond = microsecond
+ self._delta_hours = delta_hours
+ self._delta_minutes = delta_minutes
+ self._delta_seconds = delta_seconds
+ self._delta_microseconds = delta_microseconds
+
+ @property
+ def __repr_params__(self):
+ date_params = super(DateTime, self).__repr_params__
+ return (date_params[:3]
+ + (self._hour, self._minute, self._second, self._microsecond)
+ + date_params[3:-1]
+ + (self._delta_hours, self._delta_minutes, self._delta_seconds,
+ self._delta_microseconds)
+ + date_params[-1:])
+
+ def pyson(self):
+ res = super(DateTime, self).pyson()
+ res['__class__'] = 'DateTime'
+ res['h'] = self._hour
+ res['m'] = self._minute
+ res['s'] = self._second
+ res['ms'] = self._microsecond
+ res['dh'] = self._delta_hours
+ res['dm'] = self._delta_minutes
+ res['ds'] = self._delta_seconds
+ res['dms'] = self._delta_microseconds
+ return res
+
+ def types(self):
+ return {datetime.datetime}
+
+ @staticmethod
+ def eval(dct, context):
+ now = dct.get('start')
+ if (isinstance(now, datetime.date)
+ and not isinstance(now, datetime.datetime)):
+ now = datetime.datetime.combine(now, datetime.time())
+ if not isinstance(now, datetime.datetime):
+ now = datetime.datetime.utcnow()
+ return now + relativedelta(
+ year=dct['y'],
+ month=dct['M'],
+ day=dct['d'],
+ hour=dct['h'],
+ minute=dct['m'],
+ second=dct['s'],
+ microsecond=dct['ms'],
+ years=dct['dy'],
+ months=dct['dM'],
+ days=dct['dd'],
+ hours=dct['dh'],
+ minutes=dct['dm'],
+ seconds=dct['ds'],
+ microseconds=dct['dms'],
+ )
+
+
+class TimeDelta(PYSON):
+
+ def __init__(self, days=0, seconds=0, microseconds=0):
+ for i in [days, seconds, microseconds]:
+ if isinstance(i, PYSON):
+ assert i.types().issubset({int, float}), \
+ '%s must be an integer' % (i,)
+ else:
+ assert isinstance(i, (int, float)), \
+ '%s must be an integer' % (i,)
+ self._days = days
+ self._seconds = seconds
+ self._microseconds = microseconds
+
+ @property
+ def __repr_params__(self):
+ return self._days, self._seconds, self._microseconds
+
+ def pyson(self):
+ return {
+ '__class__': 'TimeDelta',
+ 'd': self._days,
+ 's': self._seconds,
+ 'm': self._microseconds,
+ }
+
+ def types(self):
+ return {datetime.timedelta}
+
+ @staticmethod
+ def eval(dct, context):
+ return datetime.timedelta(
+ days=dct['d'],
+ seconds=dct['s'],
+ microseconds=dct['m'],
+ )
+
+
+class Len(PYSON):
+
+ def __init__(self, v):
+ super(Len, self).__init__()
+ if isinstance(v, PYSON):
+ assert v.types().issubset({dict, list, str}), \
+ 'value must be a dict or a list or a string'
+ else:
+ assert isinstance(v, (dict, list, str)), \
+ 'value must be a dict or list or a string'
+ self._value = v
+
+ @property
+ def __repr_params__(self):
+ return (self._value,)
+
+ def pyson(self):
+ return {
+ '__class__': 'Len',
+ 'v': self._value,
+ }
+
+ def types(self):
+ return {int}
+
+ @staticmethod
+ def eval(dct, context):
+ return len(dct['v'])
+
+
+CONTEXT = {
+ 'Eval': Eval,
+ 'Not': Not,
+ 'Bool': Bool,
+ 'And': And,
+ 'Or': Or,
+ 'Equal': Equal,
+ 'Greater': Greater,
+ 'Less': Less,
+ 'If': If,
+ 'Get': Get,
+ 'In': In,
+ 'Date': Date,
+ 'DateTime': DateTime,
+ 'TimeDelta': TimeDelta,
+ 'Len': Len,
+ 'true': True,
+ 'false': False,
+}
diff --git a/proteus/proteus/tests/__init__.py b/proteus/proteus/tests/__init__.py
new file mode 100644
index 0000000000..30637cfdb0
--- /dev/null
+++ b/proteus/proteus/tests/__init__.py
@@ -0,0 +1,54 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import os
+import sys
+import unittest
+import doctest
+
+import proteus
+import proteus.config
+
+os.environ.setdefault('TRYTOND_DATABASE_URI', 'sqlite:///:memory:')
+os.environ.setdefault('DB_NAME', ':memory:')
+from trytond.tests.test_tryton import ( # noqa: E402
+ doctest_setup, doctest_teardown)
+
+here = os.path.dirname(__file__)
+readme = os.path.normpath(os.path.join(here, '..', '..', 'README.rst'))
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ loader = unittest.TestLoader()
+ for filename in os.listdir(os.path.dirname(__file__)):
+ if filename.startswith("test") and filename.endswith(".py"):
+ modname = "proteus.tests." + filename[:-3]
+ __import__(modname)
+ module = sys.modules[modname]
+ suite.addTests(loader.loadTestsFromModule(module))
+ suite.addTests(additional_tests())
+ return suite
+
+
+def additional_tests():
+ suite = unittest.TestSuite()
+ for mod in (proteus, proteus.config):
+ suite.addTest(doctest.DocTestSuite(mod))
+ if os.path.isfile(readme):
+ suite.addTest(doctest.DocFileSuite(readme, module_relative=False,
+ setUp=doctest_setup, tearDown=doctest_teardown,
+ encoding='utf-8',
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ return suite
+
+
+def main():
+ suite = test_suite()
+ runner = unittest.TextTestRunner()
+ return runner.run(suite)
+
+
+if __name__ == '__main__':
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__)))))
+ sys.exit(not main().wasSuccessful())
diff --git a/proteus/proteus/tests/common.py b/proteus/proteus/tests/common.py
new file mode 100644
index 0000000000..dcbcc24ec9
--- /dev/null
+++ b/proteus/proteus/tests/common.py
@@ -0,0 +1,18 @@
+# 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 unittest import TestCase
+
+from trytond.tests.test_tryton import db_exist, create_db
+
+from proteus import config
+
+
+class ProteusTestCase(TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ if not db_exist():
+ create_db()
+
+ def setUp(self):
+ self.config = config.set_trytond()
diff --git a/proteus/proteus/tests/test_config.py b/proteus/proteus/tests/test_config.py
new file mode 100644
index 0000000000..3841c860a4
--- /dev/null
+++ b/proteus/proteus/tests/test_config.py
@@ -0,0 +1,39 @@
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import proteus.config
+from .common import ProteusTestCase
+
+
+class TestConfig(ProteusTestCase):
+
+ def test_proxy(self):
+ config = proteus.config.get_config()
+ user_proxy = config.get_proxy('res.user')
+ user_id = user_proxy.search([('login', '=', 'admin')], 0, 1, None,
+ config.context)[0]
+ self.assertTrue(user_id == config.user)
+
+ def test_proxy_keyword(self):
+ config = proteus.config.get_config()
+ user_proxy = config.get_proxy('res.user')
+ user_id, = user_proxy.search(
+ [('login', '=', 'admin')], limit=1, context=config.context)
+ self.assertTrue(user_id == config.user)
+
+ def test_proxy_methods(self):
+ config = proteus.config.get_config()
+ self.assertTrue('search' in config.get_proxy_methods('res.user'))
+
+ def test_trytond_config_eq(self):
+ config1 = proteus.config.get_config()
+ proteus.config.set_trytond()
+ config2 = proteus.config.get_config()
+ self.assertEqual(config1, config2)
+
+ self.assertRaises(NotImplementedError, config1.__eq__, None)
+
+ def test_repr(self):
+ config = proteus.config.TrytondConfig('sqlite:///:memory:')
+ self.assertEqual(repr(config),
+ "proteus.config.TrytondConfig("
+ "'sqlite:///:memory:', 'admin', config_file=None)")
diff --git a/proteus/proteus/tests/test_context.py b/proteus/proteus/tests/test_context.py
new file mode 100644
index 0000000000..7cbacec4e5
--- /dev/null
+++ b/proteus/proteus/tests/test_context.py
@@ -0,0 +1,16 @@
+# 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 .common import ProteusTestCase
+
+
+class TestContext(ProteusTestCase):
+
+ def test_config(self):
+ prev_ctx = self.config._context
+
+ with self.config.set_context({'a': 1}):
+ self.assertEqual(self.config.context['a'], 1)
+ for key, value in prev_ctx.items():
+ self.assertEqual(self.config.context[key], value)
+
+ self.assertEqual(self.config.context, prev_ctx)
diff --git a/proteus/proteus/tests/test_model.py b/proteus/proteus/tests/test_model.py
new file mode 100644
index 0000000000..a60a801062
--- /dev/null
+++ b/proteus/proteus/tests/test_model.py
@@ -0,0 +1,370 @@
+# 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 proteus import Model
+from .common import ProteusTestCase
+
+
+class TestModel(ProteusTestCase):
+
+ def test_class_cache(self):
+ User1 = Model.get('res.user')
+ User2 = Model.get('res.user')
+ self.assertEqual(id(User1), id(User2))
+
+ Model.reset()
+ User3 = Model.get('res.user')
+ self.assertNotEqual(id(User1), id(User3))
+
+ def test_class_method(self):
+ User = Model.get('res.user')
+ self.assertTrue(len(User.search([('login', '=', 'admin')], {})))
+
+ def test_int(self):
+ User = Model.get('res.user')
+ admin = User.find([('login', '=', 'admin')])[0]
+ self.assertEqual(int(admin), admin.id)
+
+ def test_id_zero(self):
+ User = Model.get('res.user')
+ root = User(0)
+ self.assertEqual(root.login, 'root')
+
+ def test_find(self):
+ User = Model.get('res.user')
+ admin = User.find([('login', '=', 'admin')])[0]
+ self.assertEqual(admin.login, 'admin')
+
+ def test_many2one(self):
+ User = Model.get('res.user')
+ admin = User.find([('login', '=', 'admin')])[0]
+ self.assertTrue(isinstance(admin.create_uid, User))
+ try:
+ admin.create_uid = 'test'
+ self.fail()
+ except AssertionError:
+ pass
+ admin.create_uid = admin
+ admin.create_uid = None
+
+ User(write_uid=None)
+
+ def test_one2many(self):
+ Group = Model.get('res.group')
+ administration = Group.find([('name', '=', 'Administration')])[0]
+ self.assertTrue(isinstance(administration.model_access, list))
+ self.assertTrue(isinstance(administration.model_access[0],
+ Model.get('ir.model.access')))
+ try:
+ administration.model_access = []
+ self.fail()
+ except AttributeError:
+ pass
+
+ def test_many2many(self):
+ User = Model.get('res.user')
+ admin = User.find([('login', '=', 'admin')])[0]
+ self.assertTrue(isinstance(admin.groups, list))
+ self.assertTrue(isinstance(admin.groups[0],
+ Model.get('res.group')))
+ try:
+ admin.groups = []
+ self.fail()
+ except AttributeError:
+ pass
+
+ # TODO test date
+
+ def test_reference(self):
+ Attachment = Model.get('ir.attachment')
+ User = Model.get('res.user')
+ admin = User.find([('login', '=', 'admin')])[0]
+ attachment = Attachment()
+ attachment.name = 'Test'
+ attachment.resource = admin
+ attachment.save()
+ self.assertEqual(attachment.resource, admin)
+
+ def test_id_counter(self):
+ User = Model.get('res.user')
+ test1 = User()
+ self.assertTrue(test1.id < 0)
+ test2 = User()
+ self.assertTrue(test2.id < 0)
+ self.assertNotEqual(test1.id, test2.id)
+
+ def test_init(self):
+ User = Model.get('res.user')
+ self.assertEqual(User(1).id, 1)
+ self.assertEqual(User(name='Foo').name, 'Foo')
+
+ Lang = Model.get('ir.lang')
+ en, = Lang.find([('code', '=', 'en')])
+ self.assertEqual(User(language=en).language, en)
+ self.assertEqual(User(language=en.id).language, en)
+
+ Group = Model.get('res.group')
+ groups = Group.find()
+ self.assertEqual(len(User(groups=groups).groups), len(groups))
+ self.assertEqual(len(User(groups=[x.id for x in groups]).groups),
+ len(groups))
+
+ def test_assign_unknown_field(self):
+ "Test it is not allowed to assign unknown field"
+ User = Model.get('res.user')
+ user = User()
+
+ with self.assertRaises(AttributeError):
+ user.unknown_field = 'foo'
+
+ def test_save(self):
+ User = Model.get('res.user')
+ test = User()
+ test.name = 'Test'
+ test.login = 'test'
+ test.save()
+ self.assertTrue(test.id > 0)
+
+ test = User(test.id)
+ self.assertEqual(test.name, 'Test')
+ self.assertEqual(test.login, 'test')
+ self.assertTrue(test.active)
+
+ test.signature = 'Test signature'
+ self.assertEqual(test.signature, 'Test signature')
+ test.save()
+ self.assertEqual(test.signature, 'Test signature')
+ test = User(test.id)
+ self.assertEqual(test.signature, 'Test signature')
+
+ Group = Model.get('res.group')
+ test2 = User(name='Test 2', login='test2',
+ groups=[Group(name='Test 2')])
+ test2.save()
+ self.assertTrue(test2.id > 0)
+ self.assertEqual(test2.name, 'Test 2')
+ self.assertEqual(test2.login, 'test2')
+
+ test.signature = 'classmethod save'
+ test2.name = 'Test 2 classmethod'
+ test3 = User(name='Test 3', login='test3')
+ User.save([test, test2, test3])
+ self.assertEqual(test.signature, 'classmethod save')
+ self.assertEqual(test2.name, 'Test 2 classmethod')
+ self.assertTrue(test3.id > 0)
+ self.assertEqual(test3.name, 'Test 3')
+
+ def test_save_many2one(self):
+ User = Model.get('res.user')
+ test = User()
+ test.name = 'Test save many2one'
+ test.login = 'test_save_many2one'
+ test.save()
+
+ Lang = Model.get('ir.lang')
+ en, = Lang.find([('code', '=', 'en')])
+ test.language = en
+ test.save()
+ self.assertEqual(test.language, en)
+
+ test.language = None
+ test.save()
+ self.assertFalse(test.language)
+
+ def test_save_one2many(self):
+ Group = Model.get('res.group')
+ group = Group()
+ group.name = 'Test save one2many'
+ group.save()
+
+ ModelAccess = Model.get('ir.model.access')
+ Model_ = Model.get('ir.model')
+ model_access = ModelAccess()
+ model_access.model = Model_.find([('model', '=', 'res.group')])[0]
+ model_access.perm_read = True
+ model_access.perm_write = True
+ model_access.perm_create = True
+ model_access.perm_delete = True
+
+ group.model_access.append(model_access)
+ group.save()
+ self.assertEqual(len(group.model_access), 1)
+
+ model_access_id = group.model_access[0].id
+
+ group.name = 'Test save one2many bis'
+ group.model_access[0].description = 'Test save one2many'
+ group.save()
+ self.assertEqual(group.model_access[0].description,
+ 'Test save one2many')
+
+ group.model_access.pop()
+ group.save()
+ self.assertEqual(group.model_access, [])
+ self.assertEqual(len(ModelAccess.find([('id', '=', model_access_id)])),
+ 1)
+
+ group.model_access.append(ModelAccess(model_access_id))
+ group.save()
+ self.assertEqual(len(group.model_access), 1)
+
+ group.model_access.remove(group.model_access[0])
+ group.save()
+ self.assertEqual(group.model_access, [])
+ self.assertEqual(len(ModelAccess.find([('id', '=', model_access_id)])),
+ 0)
+
+ def test_save_many2many(self):
+ User = Model.get('res.user')
+ test = User()
+ test.name = 'Test save many2many'
+ test.login = 'test_save_many2many'
+ test.save()
+
+ Group = Model.get('res.group')
+ group = Group()
+ group.name = 'Test save many2many'
+ group.save()
+
+ test.groups.append(group)
+ test.save()
+ self.assertEqual(len(test.groups), 1)
+
+ group_id = test.groups[0].id
+
+ test.name = 'Test save many2many bis'
+ test.groups[0].name = 'Test save many2many bis'
+ test.save()
+ self.assertEqual(test.groups[0].name,
+ 'Test save many2many bis')
+
+ test.groups.pop()
+ test.save()
+ self.assertEqual(test.groups, [])
+ self.assertEqual(len(Group.find([('id', '=', group_id)])), 1)
+
+ test.groups.append(Group(group_id))
+ test.save()
+ self.assertEqual(len(test.groups), 1)
+
+ test.groups.remove(test.groups[0])
+ test.save()
+ self.assertEqual(test.groups, [])
+ self.assertEqual(len(Group.find([('id', '=', group_id)])), 0)
+
+ def test_eq(self):
+ User = Model.get('res.user')
+ test = User()
+ test.name = 'Test eq'
+ test.login = 'test_eq'
+ test.save()
+ admin1 = User.find([('login', '=', 'admin')])[0]
+ admin2 = User.find([('login', '=', 'admin')])[0]
+
+ self.assertEqual(admin1, admin2)
+ self.assertFalse(admin1 != admin2)
+ self.assertNotEqual(admin1, test)
+ self.assertFalse(admin1 == test)
+ self.assertNotEqual(admin1, None)
+ self.assertNotEqual(admin1, False)
+ self.assertNotEqual(admin1, 1)
+
+ def test_default_set(self):
+ User = Model.get('res.user')
+ Group = Model.get('res.group')
+ group_ids = [x.id for x in Group.find()]
+ test = User()
+ test._default_set({
+ 'name': 'Test',
+ 'groups': group_ids,
+ })
+ self.assertEqual(test.name, 'Test')
+ self.assertEqual([x.id for x in test.groups], group_ids)
+
+ test = User()
+ test._default_set({
+ 'name': 'Test',
+ 'groups': [
+ {
+ 'name': 'Group 1',
+ },
+ {
+ 'name': 'Group 2',
+ },
+ ],
+ })
+ self.assertEqual(test.name, 'Test')
+ self.assertEqual([x.name for x in test.groups], ['Group 1', 'Group 2'])
+
+ def test_delete(self):
+ Group = Model.get('res.group')
+ test = Group()
+ test.name = 'Test delete'
+ test.save()
+ test.delete()
+
+ def test_duplicate(self):
+ User = Model.get('res.user')
+ test = User()
+ test.name = 'Test duplicate'
+ test.login = 'test duplicate'
+ test.save()
+ copy, = User.duplicate([test], {'name': 'Test copy'})
+ self.assertEqual(copy.name, 'Test copy')
+ self.assertEqual(copy.login, 'test duplicate (copy)')
+ self.assertNotEqual(copy.id, test.id)
+
+ def test_on_change(self):
+ Trigger = Model.get('ir.trigger')
+
+ trigger = Trigger()
+
+ trigger.on_time = True
+ self.assertEqual(trigger.on_create, False)
+
+ trigger.on_create = True
+ self.assertEqual(trigger.on_time, False)
+
+ def test_on_change_with(self):
+ Attachment = Model.get('ir.attachment')
+
+ attachment = Attachment()
+
+ attachment.description = 'Test'
+ self.assertEqual(attachment.summary, 'Test')
+
+ def test_on_change_set(self):
+ User = Model.get('res.user')
+ Group = Model.get('res.group')
+
+ test = User()
+ test._on_change_set('name', 'Test')
+ self.assertEqual(test.name, 'Test')
+ group_ids = [x.id for x in Group.find()]
+ test._on_change_set('groups', group_ids)
+ self.assertEqual([x.id for x in test.groups], group_ids)
+
+ test._on_change_set('groups', {'remove': [group_ids[0]]})
+ self.assertEqual([x.id for x in test.groups], group_ids[1:])
+
+ test._on_change_set('groups', {'add': [(-1, {
+ 'name': 'Bar',
+ })]})
+ self.assertTrue([x for x in test.groups if x.name == 'Bar'])
+
+ test.groups.extend(Group.find())
+ group = test.groups[0]
+ test._on_change_set('groups', {'update': [{
+ 'id': group.id,
+ 'name': 'Foo',
+ }]})
+ self.assertTrue([x for x in test.groups if x.name == 'Foo'])
+
+ def test_model_list_find(self):
+ User = Model.get('res.user')
+ Group = Model.get('res.group')
+
+ test = User()
+ groups = test.groups.find(order=[('id', 'ASC')])
+ all_groups = Group.find([], order=[('id', 'ASC')])
+
+ self.assertListEqual(groups, all_groups)
diff --git a/proteus/proteus/tests/test_report.py b/proteus/proteus/tests/test_report.py
new file mode 100644
index 0000000000..3df9fc5d87
--- /dev/null
+++ b/proteus/proteus/tests/test_report.py
@@ -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.
+import unittest
+try:
+ import pydot
+except ImportError:
+ pydot = None
+
+from proteus import Report, Model
+from .common import ProteusTestCase
+
+
+class TestReport(ProteusTestCase):
+
+ @unittest.skipIf(not pydot, 'requires pydot')
+ def test_model_graph(self):
+ IrModel = Model.get('ir.model')
+ models = IrModel.find([])
+ data = {
+ 'level': 1,
+ 'filter': '',
+ }
+ report = Report('ir.model.graph')
+ type_, data, print_, name = report.execute(models, data)
+ self.assertEqual(type_, 'png')
+ self.assertEqual(print_, False)
+ self.assertEqual(name, 'Graph')
diff --git a/proteus/proteus/tests/test_wizard.py b/proteus/proteus/tests/test_wizard.py
new file mode 100644
index 0000000000..b36effcd62
--- /dev/null
+++ b/proteus/proteus/tests/test_wizard.py
@@ -0,0 +1,53 @@
+# 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 proteus import Wizard, Model
+from .common import ProteusTestCase
+
+
+class TestWizard(ProteusTestCase):
+
+ def test_translation_clean(self):
+ translation_clean = Wizard('ir.translation.clean')
+ self.assertEqual(translation_clean.form.__class__.__name__,
+ 'ir.translation.clean.start')
+ translation_clean.execute('clean')
+ self.assertEqual(translation_clean.form.__class__.__name__,
+ 'ir.translation.clean.succeed')
+
+ def test_translation_export(self):
+ Lang = Model.get('ir.lang')
+ Module = Model.get('ir.module')
+ translation_export = Wizard('ir.translation.export')
+ translation_export.form.language, = Lang.find([('code', '=', 'en')])
+ translation_export.form.module, = Module.find([('name', '=', 'ir')])
+ translation_export.execute('export')
+ self.assertTrue(translation_export.form.file)
+ translation_export.execute('end')
+
+ def test_user_config(self):
+ User = Model.get('res.user')
+
+ user_config = Wizard('res.user.config')
+ user_config.execute('user')
+ user_config.form.name = 'Foo'
+ user_config.form.login = 'foo'
+ user_config.execute('add')
+ self.assertEqual(user_config.form.name, None)
+ self.assertEqual(user_config.form.login, None)
+ user_config.form.name = 'Bar'
+ user_config.form.login = 'bar'
+ user_config.execute('end')
+
+ self.assertTrue(User.find([('name', '=', 'Foo')]))
+ self.assertFalse(User.find([('name', '=', 'Bar')]))
+
+ def test_translation_update(self):
+ print_model_graph = Wizard('ir.translation.update')
+ self.assertEqual(len(print_model_graph.actions), 0)
+ print_model_graph.execute('update')
+ self.assertEqual(len(print_model_graph.actions), 1)
+
+ def test_configuration_wizard(self):
+ config_wizard = Wizard('ir.module.config_wizard')
+ config_wizard.execute('action')
+ self.assertTrue(config_wizard.actions)
diff --git a/proteus/setup.py b/proteus/setup.py
new file mode 100755
index 0000000000..25f0c40fe2
--- /dev/null
+++ b/proteus/setup.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# This file is part of Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+import io
+import os
+import re
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return io.open(
+ os.path.join(os.path.dirname(__file__), fname),
+ 'r', encoding='utf-8').read()
+
+
+def get_version():
+ init = read(os.path.join('proteus', '__init__.py'))
+ return re.search('__version__ = "([0-9.]*)"', init).group(1)
+
+
+def get_require_version(name):
+ if minor_version % 2:
+ require = '%s >= %s.%s.dev0, < %s.%s'
+ else:
+ require = '%s >= %s.%s, < %s.%s'
+ require %= (name, major_version, minor_version,
+ major_version, minor_version + 1)
+ return require
+
+
+name = 'proteus'
+version = get_version()
+major_version, minor_version, _ = version.split('.', 2)
+major_version = int(major_version)
+minor_version = int(minor_version)
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+ major_version, minor_version)
+if minor_version % 2:
+ version = '%s.%s.dev0' % (major_version, minor_version)
+ download_url = 'hg+http://hg.tryton.org/%s#egg=%s-%s' % (
+ name, name, version)
+local_version = []
+if os.environ.get('CI_JOB_ID'):
+ local_version.append(os.environ['CI_JOB_ID'])
+else:
+ for build in ['CI_BUILD_NUMBER', 'CI_JOB_NUMBER']:
+ if os.environ.get(build):
+ local_version.append(os.environ[build])
+ else:
+ local_version = []
+ break
+if local_version:
+ version += '+' + '.'.join(local_version)
+
+dependency_links = []
+if minor_version % 2:
+ dependency_links.append(
+ 'https://trydevpi.tryton.org/?local_version='
+ + '.'.join(local_version))
+
+setup(name=name,
+ version=version,
+ description='Library to access Tryton server as a client',
+ long_description=read('README.rst'),
+ author='Tryton',
+ author_email='bugs@tryton.org',
+ url='http://www.tryton.org/',
+ download_url=download_url,
+ project_urls={
+ "Bug Tracker": 'https://bugs.tryton.org/',
+ "Documentation": 'https://docs.tryton.org/',
+ "Forum": 'https://www.tryton.org/forum',
+ "Source Code": 'https://hg.tryton.org/proteus',
+ },
+ keywords='tryton library cli',
+ packages=find_packages(),
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Plugins',
+ 'Framework :: Tryton',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: Financial and Insurance Industry',
+ 'Intended Audience :: Legal Industry',
+ 'License :: OSI Approved :: '
+ 'GNU Library or Lesser General Public License (LGPL)',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Programming Language :: Python :: Implementation :: PyPy',
+ 'Topic :: Office/Business',
+ ],
+ platforms='any',
+ license='LGPL-3',
+ python_requires='>=3.6',
+ install_requires=[
+ 'defusedxml',
+ "python-dateutil",
+ ],
+ extras_require={
+ 'trytond': [get_require_version('trytond')],
+ },
+ dependency_links=dependency_links,
+ zip_safe=True,
+ test_suite='proteus.tests',
+ tests_require=[get_require_version('trytond'),
+ get_require_version('trytond_party')],
+ )
diff --git a/proteus/tox.ini b/proteus/tox.ini
new file mode 100644
index 0000000000..21a156aa54
--- /dev/null
+++ b/proteus/tox.ini
@@ -0,0 +1,10 @@
+[tox]
+envlist = py36,py37,py38,py39,pypy3
+
+[testenv]
+commands = {envpython} setup.py test
+deps =
+setenv =
+ TRYTOND_DATABASE_URI={env:TRYTOND_DATBASE_URI:sqlite://}
+ DB_NAME={env:DB_NAME::memory:}
+install_command = pip install --pre --find-links https://trydevpi.tryton.org/?local_version={env:CI_JOB_ID:{env:CI_BUILD_NUMBER}.{env:CI_JOB_NUMBER}} {opts} {packages}
diff --git a/readthedocs/account.rst b/readthedocs/account.rst
new file mode 100644
index 0000000000..7c0ca10d49
--- /dev/null
+++ b/readthedocs/account.rst
@@ -0,0 +1,126 @@
+:orphan:
+
+.. _index-accounting:
+
+Accounting
+==========
+
+`Account `_
+ Fundamentals for most accounting needs.
+
+`Asset `_
+ Depreciation of fixed assets.
+
+`Belgian `_
+ Belgian accounting.
+
+`Cash Rounding `_
+ Round cash amounts.
+
+`Credit Limit `_
+ Manages credit limit of parties.
+
+`Deposit `_
+ Supports customer deposits.
+
+`Dunning `_
+ Manages dunning on receivables.
+
+`Dunning Email `_
+ Sends dunning emails.
+
+`Dunning Fee `_
+ Adds fees to dunnings.
+
+`Dunning Letter `_
+ Prints dunning letters.
+
+`Spanish `_
+ Spanish accounting.
+
+`Europe `_
+ Common European requirements.
+
+`French `_
+ French accounting.
+
+`French Chorus `_
+ Sends invoices via Chorus Pro.
+
+`German `_
+ German accounting.
+
+`Invoice `_
+ Manages customer and supplier invoices.
+
+`Invoice Correction `_
+ Correct price on posted invoices.
+
+`Invoice Defer `_
+ Defer expense and revenue.
+
+`Invoice History `_
+ Historize invoice.
+
+`Invoice Line Standalone `_
+ Supports invoice line without invoice.
+
+`Invoice Secondary Unit `_
+ Adds a secondary unit of measure.
+
+`Invoice Stock `_
+ Links invoice lines and stock moves.
+
+`Payment `_
+ Manages payments.
+
+`Payment Braintree `_
+ Receives payment from Braintree.
+
+`Payment Clearing `_
+ Uses clearing account for payments.
+
+`Payment SEPA `_
+ Genrates SEPA messages for payments.
+
+`Payment SEPA CFONB `_
+ Adds CFONB flavors to SEPA.
+
+`Payment Stripe `_
+ Receives payment from Stripe.
+
+`Product `_
+ Adds accounting on product and category.
+
+`Statement `_
+ Books bank statement, cash day book etc.
+
+`Statement AEB43 `_
+ Imports statements in AEB43 format.
+
+`Statement CODA `_
+ Imports statements in CODA format.
+
+`Statement OFX `_
+ Imports statements in OFX format.
+
+`Statement Rule `_
+ Applies rules on imported statements.
+
+`Stock Anglo-Saxon `_
+ Values stock using the anglo-saxon method.
+
+`Stock Continental `_
+ Values stock using the continental method.
+
+`Stock Landed Cost `_
+ Allocates landed cost.
+
+`Stock Landed Cost Weight `_
+ Allocates landed cost based on weight.
+
+`Tax Cash `_
+ Reports tax on cash basis.
+
+`Tax Rule Country `_
+ Applies taxes per country of origin and destination.
diff --git a/readthedocs/analytic.rst b/readthedocs/analytic.rst
new file mode 100644
index 0000000000..171a35b596
--- /dev/null
+++ b/readthedocs/analytic.rst
@@ -0,0 +1,18 @@
+:orphan:
+
+.. _index-analytic-accounting:
+
+Analytic Accounting
+===================
+
+`Account `_
+ Fundamentals for analytic.
+
+`Invoice `_
+ Adds analytic on invoice.
+
+`Purchase `_
+ Adds analytic on purchase.
+
+`Sale `_
+ Adds analytic on sale.
diff --git a/readthedocs/authentication.rst b/readthedocs/authentication.rst
new file mode 100644
index 0000000000..2f0e000c8a
--- /dev/null
+++ b/readthedocs/authentication.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _index-authentication:
+
+Authentication
+==============
+
+`SMS `_
+ Authentication per SMS.
+
+`LDAP `_
+ Authentication per LDAP.
diff --git a/readthedocs/carrier.rst b/readthedocs/carrier.rst
new file mode 100644
index 0000000000..17723a29c3
--- /dev/null
+++ b/readthedocs/carrier.rst
@@ -0,0 +1,18 @@
+:orphan:
+
+.. _index-carrier:
+
+Carrier
+=======
+
+`Carrier `_
+ Fundamentals to manage carriers.
+
+`Percentage `_
+ Computes cost based on percentage.
+
+`Subdivision `_
+ Restricts the usage of a carrier to subdivisions and ZIP codes.
+
+`Weight `_
+ Computes cost based on weight.
diff --git a/readthedocs/commission.rst b/readthedocs/commission.rst
new file mode 100644
index 0000000000..8fd18df63f
--- /dev/null
+++ b/readthedocs/commission.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _index-commission:
+
+Commission
+==========
+
+`Commission `_
+ Fundamentals to commission sale's agents.
+
+`Waiting `_
+ Creates waiting account moves.
diff --git a/readthedocs/company.rst b/readthedocs/company.rst
new file mode 100644
index 0000000000..7df11848e2
--- /dev/null
+++ b/readthedocs/company.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _index-company:
+
+Company
+=======
+
+`Company `_
+ Defines company and employees.
+
+`Work Time `_
+ Defines work time per company.
diff --git a/readthedocs/edocument.rst b/readthedocs/edocument.rst
new file mode 100644
index 0000000000..6f4fdff329
--- /dev/null
+++ b/readthedocs/edocument.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _index-edocument:
+
+Electronic Document
+===================
+
+`UN/CEFACT `_
+ Supports UN/CEFACT format.
+
+`UNECE `_
+ Adds UNECE codes.
diff --git a/readthedocs/index.rst b/readthedocs/index.rst
new file mode 100644
index 0000000000..197a42a51e
--- /dev/null
+++ b/readthedocs/index.rst
@@ -0,0 +1,117 @@
+====================
+Tryton Documentation
+====================
+
+Main
+----
+
+`Server `_
+ The main application.
+
+`Desktop Client `_
+ The application to connect to the server.
+
+`Scripting Client `_
+ The library to access models, wizards and reports like a client.
+
+Backend
+-------
+
+`GIS `_
+ Adds GIS support.
+
+Modules
+-------
+
+Operational
+^^^^^^^^^^^
+
+:ref:`Financial Accounting `
+ Reports all transactions of your chart of accounts.
+
+:ref:`Analytic Accounting `
+ Analyze your accounts.
+
+`Attendance `_
+ Track presences.
+
+:ref:`Commission `
+ Commission sale's agents.
+
+:ref:`EDocument `
+ Electronic document format.
+
+:ref:`Marketing `
+ Communicates with your customers.
+
+:ref:`Production `
+ Transforms raw materials into finished products.
+
+:ref:`Project `
+ Checks the profitability of projects.
+
+:ref:`Purchase `
+ Follows your purchases.
+
+:ref:`Sale `
+ Follows sale orders.
+
+:ref:`Sale Subscription `
+ Subscribes to recurring service.
+
+:ref:`Stock `
+ Tracks goods in your warehouses.
+
+:ref:`Timesheet `
+ Tracks time spend.
+
+Referential
+^^^^^^^^^^^
+
+`Banking `_
+ Adds bank and account.
+
+:ref:`Company `
+ Structure of the company.
+
+:ref:`Carrier `
+ To ship.
+
+`Country `_
+ ISO 3166 list of countries and subdivisions.
+
+`Currency `_
+ ISO 4217 list of currencies.
+
+`Customs `_
+ Defines customs duty per tariff code.
+
+`Dashboard `_
+ Customized dashboard.
+
+`Google Maps `_
+ Links address to Google map.
+
+`Incoterm `_
+ Defines incoterms.
+
+:ref:`Party `
+ Identify parties.
+
+:ref:`Product `
+ Manages products.
+
+Administration
+^^^^^^^^^^^^^^
+
+:ref:`Authentication `
+ Extends authentication mechanism.
+
+`Notification Email `_
+ Sends email.
+
+`User Role `_
+ Manage user groups with roles.
+
+:ref:`Web `
+ Manage web features.
diff --git a/readthedocs/marketing.rst b/readthedocs/marketing.rst
new file mode 100644
index 0000000000..877924b07d
--- /dev/null
+++ b/readthedocs/marketing.rst
@@ -0,0 +1,15 @@
+:orphan:
+
+.. _index-marketing:
+
+Marketing
+=========
+
+`Marketing `_
+ Fundamentals for marketing.
+
+`Automation `_
+ Uses scenario.
+
+`E-mail `_
+ Manage mailing lists.
diff --git a/readthedocs/party.rst b/readthedocs/party.rst
new file mode 100644
index 0000000000..1cff8e7bad
--- /dev/null
+++ b/readthedocs/party.rst
@@ -0,0 +1,18 @@
+:orphan:
+
+.. _index-party:
+
+Party
+=====
+
+`Party `_
+ Fundamentals to store parties.
+
+`Avatar `_
+ Adds avatar.
+
+`Relationship `_
+ Defines relation between parties.
+
+`SIRET `_
+ Adds SIRET and SIREN.
diff --git a/readthedocs/product.rst b/readthedocs/product.rst
new file mode 100644
index 0000000000..81ee3646da
--- /dev/null
+++ b/readthedocs/product.rst
@@ -0,0 +1,42 @@
+:orphan:
+
+.. _index-product:
+
+Product
+=======
+
+`Product `_
+ Fundamentals to manage products.
+
+`Attribute `_
+ Adds attributes to products.
+
+`Classification `_
+ Classifies products.
+
+`Classification Taxonomic `_
+ Uses Taxonomic classification.
+
+`Cost FIFO `_
+ Adds FIFO cost method.
+
+`Cost History `_
+ Historize the cost.
+
+`Cost Warehouse `_
+ Calculate cost prices per warehouse.
+
+`Measurements `_
+ Adds measurements to products.
+
+`Price List `_
+ Computes price.
+
+`Price List Dates `_
+ Sets start and end date on price lists.
+
+`Price List Parent `_
+ Adds price list cascading.
+
+`Kit `_
+ Add support for product kits.
diff --git a/readthedocs/production.rst b/readthedocs/production.rst
new file mode 100644
index 0000000000..c182d39080
--- /dev/null
+++ b/readthedocs/production.rst
@@ -0,0 +1,24 @@
+:orphan:
+
+.. _index-production:
+
+Production
+==========
+
+`Production `_
+ Fundamentals for production management.
+
+`Outsourcing `_
+ Outsources the production.
+
+`Routing `_
+ Defines routings.
+
+`Split `_
+ Split production orders.
+
+`Work `_
+ Manage work order for each production.
+
+`Work Timesheet `_
+ Track time per work.
diff --git a/readthedocs/project.rst b/readthedocs/project.rst
new file mode 100644
index 0000000000..bddc85df62
--- /dev/null
+++ b/readthedocs/project.rst
@@ -0,0 +1,18 @@
+:orphan:
+
+.. _index-project:
+
+Project
+=======
+
+`Project `_
+ Fundamentals for project management.
+
+`Invoice `_
+ Invoices the projects.
+
+`Plan `_
+ Plans the projects.
+
+`Revenue `_
+ Follows cost and revenue of projects.
diff --git a/readthedocs/purchase.rst b/readthedocs/purchase.rst
new file mode 100644
index 0000000000..bc631e249b
--- /dev/null
+++ b/readthedocs/purchase.rst
@@ -0,0 +1,36 @@
+:orphan:
+
+.. _index-purchase:
+
+Purchase
+========
+
+`Purchase `_
+ Fundamentals to follow purchases.
+
+`Amendment `_
+ Amend purchases in processing.
+
+`History `_
+ Manages revisions.
+
+`Invoice Line Standalone `_
+ Generates invoice lines.
+
+`Price List `_
+ Applies price list.
+
+`Request `_
+ Collects the purchase requests.
+
+`Request Quotation `_
+ Asks quotation for the purchase requests.
+
+`Requisition `_
+ Requires purchase by employees.
+
+`Secondary Unit `_
+ Adds a secondary unit of measure.
+
+`Shipment Cost `_
+ Computes shipment cost.
diff --git a/readthedocs/sale.rst b/readthedocs/sale.rst
new file mode 100644
index 0000000000..239ff8b245
--- /dev/null
+++ b/readthedocs/sale.rst
@@ -0,0 +1,78 @@
+:orphan:
+
+.. _index-sale:
+
+Sale
+====
+
+`Sale `_
+ Fundamentals to follow sales.
+
+`Advance Payment `_
+ Manage advance payment on sale.
+
+`Amendment `_
+ Amend sales in processing.
+
+`Complaint `_
+ Solves customer complaints.
+
+`Credit Limit `_
+ Adds sales to the credit limit.
+
+`Discount `_
+ Adds discount on lines.
+
+`Extra `_
+ Add extra lines.
+
+`Gift Card `_
+ Sales gift cards.
+
+`History `_
+ Manages revisions.
+
+`Invoice Grouping `_
+ Groups invoices.
+
+`Opportunity `_
+ Tracks leads and opportunities.
+
+`Payment `_
+ Pays sales before posting the invoice.
+
+`Price List `_
+ Applies price list.
+
+`Product Customer `_
+ Adds customer's name and code for product.
+
+`Promotion `_
+ Applies promotions.
+
+`Promotion Coupon `_
+ Applies promotions with coupons.
+
+`Shipment Cost `_
+ Computes shipment cost.
+
+`Shipment Grouping `_
+ Groups shipments.
+
+`Shipment Tolerance `_
+ Define tolerance for under and over shipment.
+
+`Secondary Unit `_
+ Adds a secondary unit of measure.
+
+`Stock Quantity `_
+ Checks stock level.
+
+`Supply `_
+ Supplies from the sale.
+
+`Supply Drop Shipment `_
+ Supplies with drop shipment.
+
+`Supply Production `_
+ Supplies with production.
diff --git a/readthedocs/sale_subscription.rst b/readthedocs/sale_subscription.rst
new file mode 100644
index 0000000000..bf6c0e0041
--- /dev/null
+++ b/readthedocs/sale_subscription.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _index-sale_subscription:
+
+Sale Subscription
+=================
+
+`Subscription `_
+ Fundamentals to manage subscriptions.
+
+`Asset `_
+ Add asset to subscribed service.
diff --git a/readthedocs/stock.rst b/readthedocs/stock.rst
new file mode 100644
index 0000000000..df456f9c54
--- /dev/null
+++ b/readthedocs/stock.rst
@@ -0,0 +1,81 @@
+:orphan:
+
+.. _index-stock:
+
+Stock
+=====
+
+`Stock `_
+ Fundamentals to manage warehouses.
+
+`Assign Manual `_
+ Manually decide where to pick products.
+
+`Consignment `_
+ Manages consignment stock from supplier or at customer warehouse.
+
+`Forecast `_
+ Forecast the stock levels.
+
+`Inventory Location `_
+ Creates inventories for many locations.
+
+`Location Move `_
+ Makes location movable.
+
+`Location Sequence `_
+ Orders locations.
+
+`Lot `_
+ Track products with lot.
+
+`Lot Shelf Life Expiration Date `_
+ Manages expiration dates.
+
+`Lot Unit `_
+ Defines unit and quantity per lot.
+
+`Package `_
+ Store packaging on shipments.
+
+`Package Shipping `_
+ Fundamentals to interact with shipping services.
+
+`Package Shipping DPD `_
+ Connects with DPD shipping service.
+
+`Package Shipping UPS `_
+ Connects with UPS shipping service.
+
+`Product Location `_
+ Defines preferred locations for products.
+
+`Quantity Early Planning `_
+ Consume earlier stock.
+
+`Quantity Issue `_
+ Reports quantity issues.
+
+`Secondary Unit `_
+ Adds a secondary unit of measure.
+
+`Shipment Cost `_
+ Adds shipment costs to outgoing moves.
+
+`Shipment Measurements `_
+ Adds measurements to shipments.
+
+`Split `_
+ Splits moves and shipments.
+
+`Supply `_
+ Supplies warehouses.
+
+`Supply Forecast `_
+ Uses forecast to supply warehouses.
+
+`Supply Day `_
+ Compute supply date per week day.
+
+`Supply Production `_
+ Supplies warehouses with production orders.
diff --git a/readthedocs/timesheet.rst b/readthedocs/timesheet.rst
new file mode 100644
index 0000000000..d53065ef30
--- /dev/null
+++ b/readthedocs/timesheet.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _index-timesheet:
+
+Timesheet
+=========
+
+`Timesheet `_
+ Fundamentals to track time spend.
+
+`Cost `_
+ Tracks employee cost.
diff --git a/readthedocs/web.rst b/readthedocs/web.rst
new file mode 100644
index 0000000000..afc47dbeab
--- /dev/null
+++ b/readthedocs/web.rst
@@ -0,0 +1,21 @@
+:orphan:
+
+.. _index-web:
+
+Web
+===
+
+`Shop `_
+ Fundamentals to sale online.
+
+`Shop Vue Storefront `_
+ Integration with Vue Storefront.
+
+`Shop Vue Storefront Stripe `_
+ Support of Stripe Payment for Vue Storefront.
+
+`Shortener `_
+ Shortens URL and connects them to actions.
+
+`User `_
+ Manages external users.
diff --git a/sao/.bower_allow_root.sh b/sao/.bower_allow_root.sh
new file mode 100755
index 0000000000..fc17f19c58
--- /dev/null
+++ b/sao/.bower_allow_root.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo '{ "allow_root": true }' > ~/.bowerrc
diff --git a/sao/.drone.yml b/sao/.drone.yml
new file mode 100644
index 0000000000..e2a994d6b3
--- /dev/null
+++ b/sao/.drone.yml
@@ -0,0 +1,17 @@
+clone:
+ hg:
+ image: plugins/hg
+ environment:
+ - HG_SHARE_POOL=/root/.cache/hg
+ volumes:
+ - cache:/root/.cache
+
+pipeline:
+ npm:
+ image: tryton/sao-test
+ commands:
+ - sh .bower_allow_root.sh
+ - npm -s install --unsafe-perm --legacy-peer-deps
+ - npm test
+ volumes:
+ - cache-npm:/root/.npm
diff --git a/sao/CHANGELOG b/sao/CHANGELOG
new file mode 100644
index 0000000000..90008764ad
--- /dev/null
+++ b/sao/CHANGELOG
@@ -0,0 +1,274 @@
+Version 6.0.24 - 2022-12-06
+---------------------------
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.23 - 2022-11-05
+---------------------------
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.22 - 2022-10-17
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.21 - 2022-08-17
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.20 - 2022-07-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.19 - 2022-06-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.18 - 2022-06-03
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.17 - 2022-05-06
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.16 - 2022-04-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.15 - 2022-03-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.14 - 2022-02-16
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.13 - 2022-02-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.12 - 2022-01-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.11 - 2022-01-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.10 - 2021-12-03
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.9 - 2021-11-04
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.8 - 2021-10-15
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.7 - 2021-10-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.6 - 2021-09-16
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.5 - 2021-09-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.4 - 2021-08-05
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.3 - 2021-07-21
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.2 - 2021-07-01
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.1 - 2021-06-17
+* Bug fixes (see mercurial logs for details)
+
+Version 6.0.0 - 2021-05-03
+* Bug fixes (see mercurial logs for details)
+* Use appropriate types for email and url widgets
+* Add completion on xxx2Many and Dict
+* Add attachment preview
+* Support document widget
+* Support icon of type URL
+* Allow sending email with existing attachments
+* Add paths and model context to action data
+* Add indentation in CSV export of tree
+* Manage which records to use for actions
+* Add drag and drop on CSV export and import
+* Remove tempusdominus dependency
+* Handle device cookie
+* Add breadcrumb as title of window form
+* Manage help for each selection
+* Display revision on dialog
+* Support empty value for timedelta converter
+* Unify PYSON string format
+
+Version 5.8.0 - 2020-11-02
+* Bug fixes (see mercurial logs for details)
+* Format timedelta in CSV export
+* Manage symbol widget
+* Fallback to model name as title
+* Set treeview footer sticky on bottom
+* Add get, in_ and contains methods to PYSON
+* Manage deletable and writable state
+* Support e-mail template
+* Always encode CSV export in UTF-8
+* Use tempusdominus and Popper for date time picker
+* Support PYSON comparison of date and datetime
+* Customize bootstrap default style
+* Set field's name as input's name attribute
+* Position copied records based on order
+* Sanitize translated RichtText fields content (issue9453)
+* Allow configuration of default colors for graph and calendar
+* Sanitize RichtText fields content (issue9405)
+* Escape external string (issue9394)
+* Keep context in sessionStorage
+* Use existing context for get_preferences
+* Escape external strings (issue9351)
+* Add context to export URL
+* Allow changes to editable tree checkboxes without first selecting the line
+
+Version 5.6.0 - 2020-05-04
+* Bug fixes (see mercurial logs for details)
+* Support link button on form
+* Add URL for the current export
+* Add option to export listed records
+* Set treeview header sticky on top
+* Add noreferrer noopener to external URL (issue9089)
+* Manage readonly dates in calendar view
+* Manage editable on calendar view
+* Manage named separator as label
+* Add MultiSelection entry to Dict field
+* Position new record based on order
+* Reset display_size of TreeView when group is cleared or changed
+* Allow relation field to be selected in CSV export
+* Hide empty cell on small screen
+* Support validate attribute of wizard's Button
+* Add infinite scroll
+
+Version 5.4.0 - 2019-11-04
+* Bug fixes (see mercurial logs for details)
+* Support visual context on tree view
+* Support multiselection field
+* Support dot notation on PYSON Eval
+* Manage sequence
+* Add drag and drop on tree views
+* Support expand on column
+* Set minimal width for columns
+* Order Dict keys by sequence
+* Support relation fields in domain parser
+* Add view modified
+* Add Dict widget for tree view
+
+Version 5.2.0 - 2019-05-06
+* Bug fixes (see mercurial logs for details)
+* Add domain inversion for reference fields
+* Add list-form view
+* Support HTML widget
+* Use locale format for data export
+* Add day view on calendar
+* Add CSV exports into print toolbar
+* Display unread and total of notes
+* Use resources method
+* Rename roundup.url configuration to bug.url
+* Use separate XML parser in Views
+* Add range selection with Shift-click
+* Use between numbers in filter box
+* Fill 'to' with 'from' value on filter box
+* Add tab domain in URL
+* Use properties
+* Allow sao classes to use javascript properties
+* Improve layout of CSV import/export
+* Add support for height and width on notebook
+* Use tab key for tab navigation
+* Use record and field properties of widget instead of method arguments
+* Reduce offset when no search result
+* Add support for plugins
+* Add support for expand, height and width attributes
+* Add drag & drop support on attachment button
+
+Version 5.0.0 - 2018-10-01
+* Bug fixes (see mercurial logs for details)
+* Prevent similar tab
+* Remove dialog for input file
+* Use dropdown for attachment
+* Manage icons in input
+* Support RTL language direction
+* Apply factor on domain parser
+* Use tab name in CSV Export/Import
+* Add bus management
+* Load custom CSS and Javascript
+* New icons
+* Support timestamp field
+* Validate dictionary items
+* Add (i)like support to domain inversion
+* Improve loading strategy by using views
+* Add support of width attribute on tree view
+* Use non conflicting keyboard shortcuts
+* Use the context to get the suffix of window name
+* Call on_change methods after setting default dtstart on calendar view
+* Allow to open in new tab Many2One and Reference with CTRL key
+* Store session in browser storage
+* Increase the space available for tab list
+* Allow to toggle menu on large screen
+* Manage URL and history
+
+Version 4.8.0 - 2018-04-23
+* Bug fixes (see mercurial logs for details)
+* Manage active field on search widget
+* Add position and length on list view
+* Reset record to its original state when discarding the popup window
+* Add support of expand attribute on group tag
+* Add the context model name in the screen context
+* Add sum of records on list view
+* Add shortcuts to Date and DateTime widgets
+* Use limit to expand tree
+* Use spellcheck and lang attributes for textbox widget
+* Support icon on page
+* Add keyword attribute to button tag
+* Support field name on image tag
+* Use input number and toLocaleString for numeric fields
+* Show related record names for all windows
+* Allow to edit Many2One from tree view
+* Use translated values when exporting Reference and Selection fields
+
+Version 4.6.0 - 2017-10-30
+* Bug fixes (see mercurial logs for details)
+* Allow to export model and record name of Reference fields
+* Re-position buttons on Binary/Image widget
+* Set focus on first visible input when modal is shown
+* Improve treeview headers
+* Add keyboard shortcuts
+* Update states of both toolbar and menu
+* Improve toolbar order
+* Use login details in title
+
+Version 4.4.0 - 2017-05-01
+* Bug fixes (see mercurial logs for details)
+* Manage help attribute on tree views
+* Manage color attribute on graph view
+* Add support for size on Many2Many
+* Allow to show plain text in password field
+
+Version 4.2.0 - 2016-11-28
+* Bug fixes (see mercurial logs for details)
+* Add PYSON Widget
+* Add CSV Import/Export
+* Manage translated field
+* Add favorites menu
+* Show records names in wizard title
+* Add support for datetime_field to Many2One, Reference
+* Add calendar view
+* Move info-bar to the top
+* Add noeval to PYSONDecoder
+* Add support for pyson_order on action window
+* Make datetimepicker locale aware
+* Restore tab default order when no manual change
+* Add support for column sorting
+* Manage confirm attribute on buttons
+* Call autocompletion when setting record value
+* Limit readonly state for xxx2Many
+* Simplify login with single database
+* Show records names in relate window title
+* Add support for count on Action Window Domains
+* Manage custom login process
+* Add erase on Many2One
+* Re-position icons on Many2One widget
+
+Version 4.0.0 - 2016-05-02
+* Bug fixes (see mercurial logs for details)
+* Timedelta uses 30 days for month
+* Manage context model of ir.action.act_window
+* Add Note
+
+Version 3.8.0 - 2015-11-02
+* Initial release
diff --git a/sao/COPYRIGHT b/sao/COPYRIGHT
new file mode 100644
index 0000000000..ae353044d7
--- /dev/null
+++ b/sao/COPYRIGHT
@@ -0,0 +1,21 @@
+Copyright (C) 2012-2022 Nicolas Évrard.
+Copyright (C) 2012-2022 Cédric Krier.
+Copyright (C) 2012-2014 Bertrand Chenal.
+Copyright (C) 2012-2022 B2CK SPRL.
+Copyright (C) 2019 Jitbit.
+Copyright (C) 2013 Thomas Park
+Copyright (C) 2020-2021 Maxime Richez
+Copyright (C) 2020-2021 SALUC SA
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
diff --git a/sao/Gruntfile.js b/sao/Gruntfile.js
new file mode 100644
index 0000000000..c33d334a8e
--- /dev/null
+++ b/sao/Gruntfile.js
@@ -0,0 +1,182 @@
+module.exports = function(grunt) {
+
+ var _ = grunt.util._;
+ var locales = ["bg", "ca", "cs", "de", "es", "es_419", "fr", "fa", "hu",
+ "it", "lo", "lt", "nl", "pl", "pt", "ru", "sl", "zh_CN"];
+ var jsfiles = [
+ 'src/sao.js',
+ 'src/rpc.js',
+ 'src/pyson.js',
+ 'src/session.js',
+ 'src/common.js',
+ 'src/model.js',
+ 'src/tab.js',
+ 'src/screen.js',
+ 'src/view.js',
+ 'src/view/form.js',
+ 'src/view/tree.js',
+ 'src/view/graph.js',
+ 'src/view/calendar.js',
+ 'src/view/list_form.js',
+ 'src/action.js',
+ 'src/window.js',
+ 'src/wizard.js',
+ 'src/board.js',
+ 'src/bus.js',
+ 'src/plugins.js',
+ 'src/html_sanitizer.js'
+ ];
+ var less_paths = [
+ 'src',
+ 'bower_components',
+ 'bower_components/bootstrap',
+ 'bower_components/bootstrap/less',
+ 'bower_components/bootstrap-rtl-ondemand/less',
+ ];
+
+ // Project configuration.
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ xgettext: {
+ locale: {
+ files: {
+ javascript: jsfiles
+ },
+ options: {
+ functionName: "Sao.i18n.gettext",
+ potFile: "locale/messages.pot"
+ },
+ }
+ },
+ shell: {
+ options: {
+ failOnError: true
+ },
+ msgmerge: {
+ command: _.map(locales, function(locale) {
+ var po = "locale/" + locale + ".po";
+ return "msgmerge -U " + po + " locale/messages.pot;";
+ }).join("")
+ }
+ },
+ po2json: {
+ options: {
+ format: 'raw'
+ },
+ all: {
+ src: ['locale/*.po'],
+ dest: 'locale/'
+ }
+ },
+ concat: {
+ dist: {
+ src: jsfiles,
+ dest: 'dist/<%= pkg.name %>.js'
+ }
+ },
+ jshint: {
+ dist: {
+ options: {
+ jshintrc: 'src/.jshintrc'
+ },
+ src: ['dist/<%= pkg.name %>.js']
+ },
+ grunt: {
+ src: ['Gruntfile.js']
+ },
+ tests: {
+ options: {
+ jshintrc: 'tests/.jshintrc'
+ },
+ src: ['tests/*.js']
+ }
+ },
+ uglify: {
+ options: {
+ banner: '/*! <%= pkg.name %>-<%= pkg.version %> | GPL-3\n' +
+ 'This file is part of Tryton. ' +
+ 'The COPYRIGHT file at the top level of\n' +
+ 'this repository contains the full copyright notices ' +
+ 'and license terms. */\n'
+ },
+ dist: {
+ src: 'dist/<%= pkg.name %>.js',
+ dest: 'dist/<%= pkg.name %>.min.js'
+ }
+ },
+ less: {
+ dev: {
+ options: {
+ paths: less_paths,
+ },
+ files: {
+ 'dist/<%= pkg.name %>.css': 'src/sao.less'
+ }
+ },
+ 'default': {
+ options: {
+ paths: less_paths,
+ yuicompress: true
+ },
+ files: {
+ 'dist/<%= pkg.name %>.min.css': 'src/sao.less'
+ }
+ }
+ },
+ watch: {
+ scripts: {
+ files: ['src/**/*.js'],
+ tasks: ['concat', 'jshint']
+ },
+ styles: {
+ files: ['src/*.less'],
+ tasks: 'less:dev'
+ }
+ },
+ qunit: {
+ options: {
+ timeout: 300000,
+ puppeteer: {
+ headless: true,
+ args: [
+ '--no-sandbox',
+ ],
+ env: {
+ TZ: 'UTC',
+ },
+ },
+ },
+ all: ['tests/*.html']
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-concat');
+ grunt.loadNpmTasks('grunt-contrib-jshint');
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+ grunt.loadNpmTasks('grunt-contrib-less');
+ grunt.loadNpmTasks('grunt-po2json');
+
+ grunt.registerTask('default', 'Build for production.', function() {
+ grunt.task.run(['concat', 'jshint', 'uglify', 'less', 'po2json']);
+ });
+ grunt.registerTask('dev', 'Build for development.', function() {
+ grunt.task.run(['concat', 'jshint', 'less:dev']);
+ });
+ grunt.registerTask('devwatch', 'Watch development', function() {
+ grunt.loadNpmTasks('grunt-contrib-watch');
+ grunt.task.run(['watch']);
+ });
+ grunt.registerTask('msgmerge', 'Update locale messages.', function() {
+ grunt.loadNpmTasks('grunt-shell');
+ grunt.task.run(['shell:msgmerge']);
+ });
+ grunt.registerTask('xgettext', ' Extracts translatable messages', function() {
+ grunt.loadNpmTasks('grunt-xgettext');
+ grunt.task.run(['xgettext']);
+ });
+ grunt.registerTask('test', 'Run tests', function() {
+ grunt.loadNpmTasks('grunt-contrib-qunit');
+ grunt.task.run(['concat', 'jshint', 'less:dev', 'qunit']);
+ });
+
+};
diff --git a/sao/LICENSE b/sao/LICENSE
new file mode 100644
index 0000000000..94a9ed024d
--- /dev/null
+++ b/sao/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/sao/README.md b/sao/README.md
new file mode 100644
index 0000000000..c4bb4865ab
--- /dev/null
+++ b/sao/README.md
@@ -0,0 +1,29 @@
+sao
+===
+
+Prerequisites
+-------------
+
+ * Node.js 0.8.0 or later (http://nodejs.org/)
+
+Installation
+------------
+
+Once you've downloaded and unpacked the sao source release, enter the directory
+where the archive was unpacked, and run:
+
+ $ npm install --production --legacy-peer-deps
+
+Development
+...........
+
+For development, you have to run instead:
+
+ $ npm install --legacy-peer-deps
+ $ grunt dev
+
+Setup
+-----
+
+Note that the entry `root` in the section `[web]` of `trytond.conf` must be set
+to the directory where the package was unpacked.
diff --git a/sao/bower.json b/sao/bower.json
new file mode 100644
index 0000000000..4933c615cc
--- /dev/null
+++ b/sao/bower.json
@@ -0,0 +1,32 @@
+{
+ "name": "sao",
+ "version": "0.0.0",
+ "authors": [
+ "Tryton"
+ ],
+ "description": "Web client for Tryton",
+ "license": "GPL",
+ "homepage": "http://www.tryton.org/",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "jquery": "^3",
+ "bootstrap": "^3.3.7",
+ "moment": "^2.10",
+ "gettext.js": "^0.7",
+ "c3": "^0.7",
+ "papaparse": "^5.0",
+ "fullcalendar": "^3.10.2",
+ "mousetrap": "^1.6",
+ "bootstrap-rtl-ondemand": "^3.3.4-ondemand",
+ "Sortable": "sortablejs#^1.8.4"
+ },
+ "devDependencies": {
+ "qunit": "^1.18"
+ }
+}
diff --git a/sao/images/LICENSE b/sao/images/LICENSE
new file mode 100644
index 0000000000..7a4a3ea242
--- /dev/null
+++ b/sao/images/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/sao/images/tryton-add.svg b/sao/images/tryton-add.svg
new file mode 100644
index 0000000000..08cc2308c8
--- /dev/null
+++ b/sao/images/tryton-add.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-archive.svg b/sao/images/tryton-archive.svg
new file mode 100644
index 0000000000..a5d45991b7
--- /dev/null
+++ b/sao/images/tryton-archive.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-arrow-down.svg b/sao/images/tryton-arrow-down.svg
new file mode 100644
index 0000000000..c1204da1cd
--- /dev/null
+++ b/sao/images/tryton-arrow-down.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/sao/images/tryton-arrow-left.svg b/sao/images/tryton-arrow-left.svg
new file mode 100644
index 0000000000..e084870266
--- /dev/null
+++ b/sao/images/tryton-arrow-left.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/sao/images/tryton-arrow-right.svg b/sao/images/tryton-arrow-right.svg
new file mode 100644
index 0000000000..0f56374381
--- /dev/null
+++ b/sao/images/tryton-arrow-right.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/sao/images/tryton-arrow-up.svg b/sao/images/tryton-arrow-up.svg
new file mode 100644
index 0000000000..b3d21d3434
--- /dev/null
+++ b/sao/images/tryton-arrow-up.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-attach.svg b/sao/images/tryton-attach.svg
new file mode 100644
index 0000000000..23979032c2
--- /dev/null
+++ b/sao/images/tryton-attach.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-back.svg b/sao/images/tryton-back.svg
new file mode 100644
index 0000000000..7ff9f9c11a
--- /dev/null
+++ b/sao/images/tryton-back.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-bookmark-border.svg b/sao/images/tryton-bookmark-border.svg
new file mode 100644
index 0000000000..abcd01628a
--- /dev/null
+++ b/sao/images/tryton-bookmark-border.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-bookmark.svg b/sao/images/tryton-bookmark.svg
new file mode 100644
index 0000000000..8b5a0a5539
--- /dev/null
+++ b/sao/images/tryton-bookmark.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-bookmarks.svg b/sao/images/tryton-bookmarks.svg
new file mode 100644
index 0000000000..79839be669
--- /dev/null
+++ b/sao/images/tryton-bookmarks.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-cancel.svg b/sao/images/tryton-cancel.svg
new file mode 100644
index 0000000000..b2dac49d45
--- /dev/null
+++ b/sao/images/tryton-cancel.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-clear.svg b/sao/images/tryton-clear.svg
new file mode 100644
index 0000000000..289d406e75
--- /dev/null
+++ b/sao/images/tryton-clear.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-close.svg b/sao/images/tryton-close.svg
new file mode 100644
index 0000000000..289d406e75
--- /dev/null
+++ b/sao/images/tryton-close.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-copy.svg b/sao/images/tryton-copy.svg
new file mode 100644
index 0000000000..dbd8b0cfc2
--- /dev/null
+++ b/sao/images/tryton-copy.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-create.svg b/sao/images/tryton-create.svg
new file mode 100644
index 0000000000..f1b885f1c2
--- /dev/null
+++ b/sao/images/tryton-create.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-date.svg b/sao/images/tryton-date.svg
new file mode 100644
index 0000000000..1aab65c4e6
--- /dev/null
+++ b/sao/images/tryton-date.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-delete.svg b/sao/images/tryton-delete.svg
new file mode 100644
index 0000000000..9308f4df56
--- /dev/null
+++ b/sao/images/tryton-delete.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-drag.svg b/sao/images/tryton-drag.svg
new file mode 100644
index 0000000000..b8d36b16f0
--- /dev/null
+++ b/sao/images/tryton-drag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sao/images/tryton-email.svg b/sao/images/tryton-email.svg
new file mode 100644
index 0000000000..e1560c6374
--- /dev/null
+++ b/sao/images/tryton-email.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-error.svg b/sao/images/tryton-error.svg
new file mode 100644
index 0000000000..19c6c83163
--- /dev/null
+++ b/sao/images/tryton-error.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-exit.svg b/sao/images/tryton-exit.svg
new file mode 100644
index 0000000000..dbd95bfd9c
--- /dev/null
+++ b/sao/images/tryton-exit.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-export.svg b/sao/images/tryton-export.svg
new file mode 100644
index 0000000000..41628798b2
--- /dev/null
+++ b/sao/images/tryton-export.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-filter.svg b/sao/images/tryton-filter.svg
new file mode 100644
index 0000000000..f0a2e4f31f
--- /dev/null
+++ b/sao/images/tryton-filter.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-align-center.svg b/sao/images/tryton-format-align-center.svg
new file mode 100644
index 0000000000..75873e9185
--- /dev/null
+++ b/sao/images/tryton-format-align-center.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-align-justify.svg b/sao/images/tryton-format-align-justify.svg
new file mode 100644
index 0000000000..288727edfc
--- /dev/null
+++ b/sao/images/tryton-format-align-justify.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-align-left.svg b/sao/images/tryton-format-align-left.svg
new file mode 100644
index 0000000000..97645a3295
--- /dev/null
+++ b/sao/images/tryton-format-align-left.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-align-right.svg b/sao/images/tryton-format-align-right.svg
new file mode 100644
index 0000000000..c073397046
--- /dev/null
+++ b/sao/images/tryton-format-align-right.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-bold.svg b/sao/images/tryton-format-bold.svg
new file mode 100644
index 0000000000..ca6b4b63ce
--- /dev/null
+++ b/sao/images/tryton-format-bold.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-color-text.svg b/sao/images/tryton-format-color-text.svg
new file mode 100644
index 0000000000..4bda2cb107
--- /dev/null
+++ b/sao/images/tryton-format-color-text.svg
@@ -0,0 +1,5 @@
+
diff --git a/sao/images/tryton-format-italic.svg b/sao/images/tryton-format-italic.svg
new file mode 100644
index 0000000000..fa65b97d56
--- /dev/null
+++ b/sao/images/tryton-format-italic.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-format-underline.svg b/sao/images/tryton-format-underline.svg
new file mode 100644
index 0000000000..8615187f44
--- /dev/null
+++ b/sao/images/tryton-format-underline.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-forward.svg b/sao/images/tryton-forward.svg
new file mode 100644
index 0000000000..2ca445703a
--- /dev/null
+++ b/sao/images/tryton-forward.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-history.svg b/sao/images/tryton-history.svg
new file mode 100644
index 0000000000..05f201a445
--- /dev/null
+++ b/sao/images/tryton-history.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-icon.png b/sao/images/tryton-icon.png
new file mode 100644
index 0000000000..f26821a927
Binary files /dev/null and b/sao/images/tryton-icon.png differ
diff --git a/sao/images/tryton-import.svg b/sao/images/tryton-import.svg
new file mode 100644
index 0000000000..f093479f99
--- /dev/null
+++ b/sao/images/tryton-import.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-info.svg b/sao/images/tryton-info.svg
new file mode 100644
index 0000000000..5b31235b88
--- /dev/null
+++ b/sao/images/tryton-info.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-launch.svg b/sao/images/tryton-launch.svg
new file mode 100644
index 0000000000..7479e92628
--- /dev/null
+++ b/sao/images/tryton-launch.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-link.svg b/sao/images/tryton-link.svg
new file mode 100644
index 0000000000..08d13d80da
--- /dev/null
+++ b/sao/images/tryton-link.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-log.svg b/sao/images/tryton-log.svg
new file mode 100644
index 0000000000..3bcd81e84c
--- /dev/null
+++ b/sao/images/tryton-log.svg
@@ -0,0 +1,5 @@
+
diff --git a/sao/images/tryton-menu.svg b/sao/images/tryton-menu.svg
new file mode 100644
index 0000000000..37e9cfb552
--- /dev/null
+++ b/sao/images/tryton-menu.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-note.svg b/sao/images/tryton-note.svg
new file mode 100644
index 0000000000..a10719ede4
--- /dev/null
+++ b/sao/images/tryton-note.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-ok.svg b/sao/images/tryton-ok.svg
new file mode 100644
index 0000000000..535ff0390a
--- /dev/null
+++ b/sao/images/tryton-ok.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-open.svg b/sao/images/tryton-open.svg
new file mode 100644
index 0000000000..f33176ea28
--- /dev/null
+++ b/sao/images/tryton-open.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-print.svg b/sao/images/tryton-print.svg
new file mode 100644
index 0000000000..7ffc5851fd
--- /dev/null
+++ b/sao/images/tryton-print.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-public.svg b/sao/images/tryton-public.svg
new file mode 100644
index 0000000000..24d6e503ed
--- /dev/null
+++ b/sao/images/tryton-public.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-question.svg b/sao/images/tryton-question.svg
new file mode 100644
index 0000000000..ac7a46002d
--- /dev/null
+++ b/sao/images/tryton-question.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sao/images/tryton-refresh.svg b/sao/images/tryton-refresh.svg
new file mode 100644
index 0000000000..e9618ab4f9
--- /dev/null
+++ b/sao/images/tryton-refresh.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-remove.svg b/sao/images/tryton-remove.svg
new file mode 100644
index 0000000000..ab7f7ba90e
--- /dev/null
+++ b/sao/images/tryton-remove.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-save.svg b/sao/images/tryton-save.svg
new file mode 100644
index 0000000000..3754a0cf0d
--- /dev/null
+++ b/sao/images/tryton-save.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-search.svg b/sao/images/tryton-search.svg
new file mode 100644
index 0000000000..6859898f79
--- /dev/null
+++ b/sao/images/tryton-search.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-send.svg b/sao/images/tryton-send.svg
new file mode 100644
index 0000000000..033ab7a11a
--- /dev/null
+++ b/sao/images/tryton-send.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sao/images/tryton-star-border.svg b/sao/images/tryton-star-border.svg
new file mode 100644
index 0000000000..21862f2cd7
--- /dev/null
+++ b/sao/images/tryton-star-border.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-star.svg b/sao/images/tryton-star.svg
new file mode 100644
index 0000000000..2b5e942ef8
--- /dev/null
+++ b/sao/images/tryton-star.svg
@@ -0,0 +1,5 @@
+
diff --git a/sao/images/tryton-switch.svg b/sao/images/tryton-switch.svg
new file mode 100644
index 0000000000..cc607585b9
--- /dev/null
+++ b/sao/images/tryton-switch.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-translate.svg b/sao/images/tryton-translate.svg
new file mode 100644
index 0000000000..e0ed1220a3
--- /dev/null
+++ b/sao/images/tryton-translate.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-unarchive.svg b/sao/images/tryton-unarchive.svg
new file mode 100644
index 0000000000..765b534c0a
--- /dev/null
+++ b/sao/images/tryton-unarchive.svg
@@ -0,0 +1,9 @@
+
diff --git a/sao/images/tryton-undo.svg b/sao/images/tryton-undo.svg
new file mode 100644
index 0000000000..6763ef40d0
--- /dev/null
+++ b/sao/images/tryton-undo.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/images/tryton-warning.svg b/sao/images/tryton-warning.svg
new file mode 100644
index 0000000000..4a0fa05996
--- /dev/null
+++ b/sao/images/tryton-warning.svg
@@ -0,0 +1,4 @@
+
diff --git a/sao/index.html b/sao/index.html
new file mode 100644
index 0000000000..302f961cf4
--- /dev/null
+++ b/sao/index.html
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+ Tryton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sao/locale/bg.po b/sao/locale/bg.po
new file mode 100644
index 0000000000..f49926a686
--- /dev/null
+++ b/sao/locale/bg.po
@@ -0,0 +1,649 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+#, fuzzy
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+#, fuzzy
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr ""
+
+msgid "Created at:"
+msgstr ""
+
+msgid "Edited by:"
+msgstr ""
+
+msgid "Edited at:"
+msgstr ""
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr ""
+
+msgid "Created at"
+msgstr ""
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ""
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+#, fuzzy
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/ca.po b/sao/locale/ca.po
new file mode 100644
index 0000000000..42a3a5698c
--- /dev/null
+++ b/sao/locale/ca.po
@@ -0,0 +1,667 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Apr 06 2018 22:57:49 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Oct 09 2017 19:35:04 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr "Esteu segur que voleu sortir?"
+
+msgid "Favorites"
+msgstr "Preferits"
+
+msgid "Manage..."
+msgstr "Gestiona..."
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "Sortir"
+
+msgid "Action"
+msgstr "Acció"
+
+msgid "New"
+msgstr "Nou"
+
+msgid "Save"
+msgstr "Desa"
+
+msgid "Switch"
+msgstr "Canvia la vista"
+
+msgid "Reload/Undo"
+msgstr "Recarrega/Desfés"
+
+msgid "Duplicate"
+msgstr "Duplica"
+
+msgid "Delete"
+msgstr "Elimina"
+
+msgid "Previous"
+msgstr "Anterior"
+
+msgid "Next"
+msgstr "Següent"
+
+msgid "Search"
+msgstr "Cerca"
+
+msgid "Close Tab"
+msgstr "Tanca pestanya"
+
+msgid "Attachment"
+msgstr "Adjunt"
+
+msgid "Note"
+msgstr "Nota"
+
+msgid "Relate"
+msgstr "Relacionat"
+
+msgid "Print"
+msgstr "Imprimeix"
+
+msgid "E-Mail"
+msgstr "Correu electrònic"
+
+msgid "Previous tab"
+msgstr "Pestanya anterior"
+
+msgid "Next tab"
+msgstr "Pestanya següent"
+
+msgid "Global search"
+msgstr "Cerca global"
+
+msgid "Show this help"
+msgstr "Mostra aquesta ajuda"
+
+msgid "Keyboard shortcuts"
+msgstr "Dreceres de teclat"
+
+msgid "Close"
+msgstr "Tanca"
+
+msgid "Global shortcuts"
+msgstr "Dreceres globals"
+
+msgid "Tab shortcuts"
+msgstr "Dreceres de la pestanya"
+
+msgid "Unable to reach the server."
+msgstr "No s'ha pogut connectar amb el servidor."
+
+msgid "Too many requests. Try again later."
+msgstr "Massa peticions. Proveu-ho de nou més tard."
+
+msgid "Login"
+msgstr "Accedeix"
+
+msgid "User name"
+msgstr "Nom d'usuari"
+
+msgid "Database"
+msgstr "Base de dades"
+
+msgid "Your selection:"
+msgstr "La vostra selecció:"
+
+msgid "Cancel"
+msgstr "Cancel·la"
+
+msgid "OK"
+msgstr "D'acord"
+
+msgid "Y"
+msgstr "A"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "S"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Per: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "a"
+
+msgid "Yes"
+msgstr "Si"
+
+msgid "True"
+msgstr "Verdader"
+
+msgid "t"
+msgstr "v"
+
+msgid "False"
+msgstr "Fals"
+
+msgid "Message: "
+msgstr "Missatge: "
+
+msgid "Warning: "
+msgstr "Avs: "
+
+msgid "Always ignore this warning."
+msgstr "Ignora sempre aquest advertiment."
+
+msgid "Do you want to proceed?"
+msgstr "Voleu continuar?"
+
+msgid "No"
+msgstr "No"
+
+msgid "Confirmation: "
+msgstr "Confirmaci: "
+
+msgid "Concurrency Exception"
+msgstr "Excepció de concurrència"
+
+msgid "Write Concurrency Warning: "
+msgstr "Avís de concurrència d'escriptura: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Aquest registre ha estat modificat mentre l'editàveu."
+
+msgid "Choose:"
+msgstr "Trieu:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Cancel·la\" per no desar;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Compara\" per veure la versi modificada;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Desa de totes formes\" per desar els vostres canvis."
+
+msgid "Compare"
+msgstr "Compara"
+
+msgid "Compare: %1"
+msgstr "Compara: %1"
+
+msgid "Write Anyway"
+msgstr "Desa de totes formes"
+
+msgid "Application Error"
+msgstr "Error d'aplicació"
+
+msgid "Report Bug"
+msgstr "Informa de l'error"
+
+msgid "Processing"
+msgstr "Processant"
+
+msgid "Search..."
+msgstr "Cerca..."
+
+msgid "Create..."
+msgstr "Crea..."
+
+msgid "Download"
+msgstr "Baixa"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Tipus de lletra"
+
+msgid "Size"
+msgstr "Mida"
+
+msgid "Switch view"
+msgstr "Canvia la vista"
+
+msgid "Previous Record"
+msgstr "Registre anterior"
+
+msgid "Next Record"
+msgstr "Registre següent"
+
+msgid "Create a new record"
+msgstr "Crea un nou registre"
+
+msgid "Save this record"
+msgstr "Desa aquest registre"
+
+msgid "Reload"
+msgstr "Recarrega"
+
+msgid "View Logs..."
+msgstr "Veure registre..."
+
+msgid "Show revisions..."
+msgstr "Mostra revisions..."
+
+msgid "Add an attachment to the record"
+msgstr "Afegeix un adjunt al registre"
+
+msgid "Add a note to the record"
+msgstr "Afegeix una nota al registre"
+
+msgid "E-Mail..."
+msgstr "Correu electrònic..."
+
+msgid "Send an e-mail using the record"
+msgstr "Envia un correu electrònic utilitzant el registre"
+
+msgid "Export"
+msgstr "Exporta"
+
+msgid "Import"
+msgstr "Importa"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"L'acció seleccionada requereix tancar totes les pestanyes.\n"
+"Voleu continuar?"
+
+msgid "Launch action"
+msgstr "Executa acció"
+
+msgid "Open related records"
+msgstr "Obre registres relacionats"
+
+msgid "Print report"
+msgstr "Imprimeix informe"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Aquest registre ha estat modificat.\n"
+"Voleu desar-lo?"
+
+msgid "Record saved."
+msgstr "Registre desat."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Està treballant amb un registre(s) duplicat."
+
+msgid "Are you sure to remove this record?"
+msgstr "Esteu segur que voleu eliminar aquest registre?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Esteu segur que voleu eliminar aquests registres?"
+
+msgid "Records removed."
+msgstr "Registres eliminats."
+
+msgid "Records not removed."
+msgstr "Els registres no s'han eliminat."
+
+msgid "You have to select one record."
+msgstr "Ha de seleccionar un registre."
+
+msgid "ID:"
+msgstr "Identificador:"
+
+msgid "Created by:"
+msgstr "Creat per:"
+
+msgid "Created at:"
+msgstr "Creat el:"
+
+msgid "Edited by:"
+msgstr "Editat per:"
+
+msgid "Edited at:"
+msgstr "Editat el:"
+
+msgid "Model: "
+msgstr "Model: "
+
+msgid "Add..."
+msgstr "Afegeix..."
+
+msgid "Preview"
+msgstr "Previsualitza"
+
+msgid "Template"
+msgstr "Plantilla"
+
+msgid "Attachment (%1)"
+msgstr "Adjunts (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Nota (%1/%2)"
+
+msgid " of "
+msgstr " de "
+
+msgid "Filters"
+msgstr "Filtres"
+
+msgid "Clear Search"
+msgstr "Netejar la cerca"
+
+msgid "Bookmarks"
+msgstr "Cerques preferides"
+
+msgid "Remove this bookmark"
+msgstr "Elimina de les cerques preferides"
+
+msgid "Bookmark this filter"
+msgstr "Desa com a cerca preferida"
+
+msgid "Bookmark Name:"
+msgstr "Nom de la cerca preferida:"
+
+msgid "Show active records"
+msgstr "Mostra registres actius"
+
+msgid "Show inactive records"
+msgstr "Mostra registres inactius"
+
+msgid "Find"
+msgstr "Cerca"
+
+msgid "From"
+msgstr "De"
+
+msgid "To"
+msgstr "A"
+
+msgid "Open the calendar"
+msgstr "Obre el calendari"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Creat per"
+
+msgid "Created at"
+msgstr "Creat el"
+
+msgid "Modified by"
+msgstr "Modificat per"
+
+msgid "Modified at"
+msgstr "Modificat el"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" s obligatori."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" no és vàlid segons el seu domini."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "Els valors de \"%1\" no són vàlids."
+
+msgid "Pre-validation"
+msgstr "Prevalidació"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Tradueix"
+
+msgid "Edit"
+msgstr "Edita"
+
+msgid "Fuzzy"
+msgstr "Dubtosa"
+
+msgid "You need to save the record before adding translations."
+msgstr "Heu de desar el registre abans d'afegir traduccions."
+
+msgid "No other language available."
+msgstr "No hi ha cap altre idioma disponible."
+
+msgid "Show"
+msgstr "Mostra"
+
+msgid "Hide"
+msgstr "Amaga"
+
+msgid "Open the record"
+msgstr "Obre el registre"
+
+msgid "Clear the field"
+msgstr "Buida el camp"
+
+msgid "Search a record"
+msgstr "Cerca un registre"
+
+msgid "Add"
+msgstr "Afegeix"
+
+msgid "Remove"
+msgstr "Elimina"
+
+msgid "Open"
+msgstr "Obre"
+
+msgid "Undelete"
+msgstr "Recupera"
+
+msgid "Save As"
+msgstr "Anomena i desa"
+
+msgid "Save As..."
+msgstr "Desa com..."
+
+msgid "Select"
+msgstr "Selecciona"
+
+msgid "Select..."
+msgstr "Selecciona..."
+
+msgid "Clear"
+msgstr "Neteja"
+
+msgid "Choose a language"
+msgstr "Escull un idioma"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Més"
+
+msgid "Today"
+msgstr "Avui"
+
+msgid "Month"
+msgstr "Mes"
+
+msgid "Week"
+msgstr "Setmana"
+
+msgid "Day"
+msgstr "Dia"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Seleccioneu la vostra acció"
+
+msgid "No action defined."
+msgstr "No s'ha definit cap acció."
+
+msgid "Attachments (%1)"
+msgstr "Adjunts (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notes (%1)"
+
+msgid "Search %1"
+msgstr "Cerca %1"
+
+msgid "Revision"
+msgstr "Revisió"
+
+msgid "All Fields"
+msgstr "Tots els camps"
+
+msgid "Fields Selected"
+msgstr "Camps seleccionats"
+
+msgid "CSV Parameters"
+msgstr "Paràmetres CSV"
+
+msgid "Delimiter:"
+msgstr "Separador:"
+
+msgid "Quote Char:"
+msgstr "Delimitador de text:"
+
+msgid "CSV Import: %1"
+msgstr "Importació CSV: %1"
+
+msgid "Auto-Detect"
+msgstr "Detecta automàticament"
+
+msgid "File to Import"
+msgstr "Fitxer a importar"
+
+msgid "Encoding:"
+msgstr "Codificació:"
+
+msgid "Lines to Skip:"
+msgstr "Línies a ometre:"
+
+msgid "You must select an import file first."
+msgstr "Primer heu de seleccionar un fitxer a importar."
+
+msgid "An error occured in loading the file."
+msgstr "S'ha produït un error al carregar el fitxer."
+
+msgid "Error processing the file at field %1."
+msgstr "S'ha produït un error al processar el fitxer al camp %1."
+
+msgid "Error"
+msgstr "Error"
+
+msgid "Error occured in loading the file"
+msgstr "S'ha produït un error al carregar el fitxer"
+
+msgid "CSV Export: %1"
+msgstr "Exportació CSV: %1"
+
+msgid "Save Export"
+msgstr "Desa l'exportació"
+
+msgid "URL Export"
+msgstr "URL Exportació"
+
+msgid "Delete Export"
+msgstr "Elimina l'exportació"
+
+msgid "Predefined Exports"
+msgstr "Exportacions predefinides"
+
+msgid "Selected Records"
+msgstr "Registres seleccionats"
+
+msgid "Listed Records"
+msgstr "Registres llistats"
+
+msgid "Export:"
+msgstr "Exportar:"
+
+msgid "Ignore search limit"
+msgstr "Ignora límit de cerca"
+
+msgid "Use locale format"
+msgstr "Utilitza format local"
+
+msgid "Add Field Names"
+msgstr "Afegeix noms dels camps"
+
+msgid "%1 (string)"
+msgstr "%1 (cadena)"
+
+msgid "%1 (model name)"
+msgstr "%1 (nom del model)"
+
+msgid "%1 (record name)"
+msgstr "%1 (nom del registre)"
+
+msgid "What is the name of this export?"
+msgstr "Quin és el nom d'aquesta exportació?"
+
+msgid "Override %1 definition?"
+msgstr "Voleu sobreescriure la definició %1?"
+
+msgid "E-mail %1"
+msgstr "Correu electrònic %1"
+
+msgid "To:"
+msgstr "A:"
+
+msgid "Cc:"
+msgstr "Cc:"
+
+msgid "Bcc:"
+msgstr "Cco:"
+
+msgid "Subject:"
+msgstr "Assumpte:"
+
+msgid "Reports"
+msgstr "Informes"
+
+msgid "Attachments"
+msgstr "Adjunts"
+
+msgid "Files"
+msgstr "Fitxers"
+
+msgid "Send"
+msgstr "Envia"
+
+msgid "Remove File"
+msgstr "Elimina fitxer"
+
+msgid "Wizard"
+msgstr "Assistent"
+
+msgid "Translate view"
+msgstr "Tradueix la vista"
diff --git a/sao/locale/cs.po b/sao/locale/cs.po
new file mode 100644
index 0000000000..f49926a686
--- /dev/null
+++ b/sao/locale/cs.po
@@ -0,0 +1,649 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+#, fuzzy
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+#, fuzzy
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr ""
+
+msgid "Created at:"
+msgstr ""
+
+msgid "Edited by:"
+msgstr ""
+
+msgid "Edited at:"
+msgstr ""
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr ""
+
+msgid "Created at"
+msgstr ""
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ""
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+#, fuzzy
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/de.po b/sao/locale/de.po
new file mode 100644
index 0000000000..29fd9500a2
--- /dev/null
+++ b/sao/locale/de.po
@@ -0,0 +1,686 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr "Wollen Sie die Seite wirklich verlassen?"
+
+msgid "Favorites"
+msgstr "Favoriten"
+
+msgid "Manage..."
+msgstr "Verwalten..."
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "Abmelden"
+
+msgid "Action"
+msgstr "Aktion"
+
+msgid "New"
+msgstr "Neu"
+
+msgid "Save"
+msgstr "Speichern"
+
+msgid "Switch"
+msgstr "Ansicht wechseln"
+
+msgid "Reload/Undo"
+msgstr "Neu laden/Rückgängig"
+
+msgid "Duplicate"
+msgstr "Duplizieren"
+
+msgid "Delete"
+msgstr "Löschen"
+
+msgid "Previous"
+msgstr "Vorheriger"
+
+msgid "Next"
+msgstr "Nächster"
+
+msgid "Search"
+msgstr "Suchen"
+
+msgid "Close Tab"
+msgstr "Registerkarte schließen"
+
+msgid "Attachment"
+msgstr "Anhang"
+
+msgid "Note"
+msgstr "Notiz"
+
+msgid "Relate"
+msgstr "Beziehung"
+
+msgid "Print"
+msgstr "Drucken"
+
+msgid "E-Mail"
+msgstr "E-Mail"
+
+msgid "Previous tab"
+msgstr "Vorherige Registerkarte"
+
+msgid "Next tab"
+msgstr "Nächste Registerkarte"
+
+msgid "Global search"
+msgstr "Golbale Suche"
+
+msgid "Show this help"
+msgstr "Diese Hilfe anzeigen"
+
+msgid "Keyboard shortcuts"
+msgstr "Tastenkombinationen"
+
+msgid "Close"
+msgstr "Schließen"
+
+msgid "Global shortcuts"
+msgstr "Globale Tastenkombinationen"
+
+msgid "Tab shortcuts"
+msgstr "Registerkarten Tastenkombinationen"
+
+msgid "Unable to reach the server."
+msgstr "Kann nicht zum Server verbinden."
+
+msgid "Too many requests. Try again later."
+msgstr "Zu viele Anfragen. Bitte versuchen Sie es später erneut."
+
+msgid "Login"
+msgstr "Log-in"
+
+msgid "User name"
+msgstr "Benutzername"
+
+msgid "Database"
+msgstr "Datenbank"
+
+msgid "Your selection:"
+msgstr "Ihre Auswahl:"
+
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "J"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "W"
+
+msgid "d"
+msgstr "t"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Von: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "J"
+
+msgid "Yes"
+msgstr "Ja"
+
+msgid "True"
+msgstr "Wahr"
+
+msgid "t"
+msgstr "w"
+
+msgid "False"
+msgstr "Falsch"
+
+msgid "Message: "
+msgstr "Nachricht: "
+
+msgid "Warning: "
+msgstr "Warnung: "
+
+msgid "Always ignore this warning."
+msgstr "Diese Warnung künftig nicht mehr anzeigen."
+
+msgid "Do you want to proceed?"
+msgstr "Fortfahren?"
+
+msgid "No"
+msgstr "Nein"
+
+msgid "Confirmation: "
+msgstr "Bestätigung: "
+
+msgid "Concurrency Exception"
+msgstr "Aktualisierungskonflikt"
+
+msgid "Write Concurrency Warning: "
+msgstr "Aktualisierungskonflikt beim Schreiben des aktuellen Datensatzes: "
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+"Dieser Datensatz wurde anderweitig geändert, während Sie ihn bearbeitet "
+"haben."
+
+msgid "Choose:"
+msgstr "Lösungsmöglichkeiten:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Abbrechen\" um den Speichervorgang abzubrechen;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Vergleichen\" um die geänderte Version des Datensatzes zu betrachten;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+"- \"Überschreiben\" um die gespeicherte Version mit Ihrer Version zu "
+"überschreiben."
+
+msgid "Compare"
+msgstr "Vergleichen"
+
+msgid "Compare: %1"
+msgstr "Vergleichen: %s"
+
+msgid "Write Anyway"
+msgstr "Überschreiben"
+
+msgid "Application Error"
+msgstr "Anwendungsfehler"
+
+msgid "Report Bug"
+msgstr "Fehler melden"
+
+msgid "Processing"
+msgstr "Verarbeitung läuft…"
+
+msgid "Search..."
+msgstr "Suche..."
+
+msgid "Create..."
+msgstr "Erstellen..."
+
+msgid "Download"
+msgstr "Herunterladen"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Schriftart"
+
+msgid "Size"
+msgstr "Größe"
+
+msgid "Switch view"
+msgstr "Ansicht wechseln"
+
+msgid "Previous Record"
+msgstr "Vorheriger Datensatz"
+
+msgid "Next Record"
+msgstr "Nächster Datensatz"
+
+msgid "Create a new record"
+msgstr "Neuen Datensatz erstellen"
+
+msgid "Save this record"
+msgstr "Diesen Datensatz speichern"
+
+msgid "Reload"
+msgstr "Neu laden"
+
+msgid "View Logs..."
+msgstr "Protokoll ansehen..."
+
+msgid "Show revisions..."
+msgstr "Bearbeitungen anzeigen..."
+
+msgid "Add an attachment to the record"
+msgstr "Einen Anhang zum Datensatz hinzufügen"
+
+msgid "Add a note to the record"
+msgstr "Eine Notiz zu einem Datensatz hinzufügen"
+
+msgid "E-Mail..."
+msgstr "E-Mail..."
+
+msgid "Send an e-mail using the record"
+msgstr "Eine E-Mail basierend auf diesem Datensatz versenden"
+
+msgid "Export"
+msgstr "Export"
+
+msgid "Import"
+msgstr "Import"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Die folgende Aktion erfordert die Schließung aller Registerkarten.\n"
+"Fortfahren?"
+
+msgid "Launch action"
+msgstr "Aktion ausführen"
+
+msgid "Open related records"
+msgstr "Datensätze einer Beziehung öffnen"
+
+msgid "Print report"
+msgstr "Bericht drucken"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Datensatz geändert.\n"
+"Soll der Datensatz gespeichert werden?"
+
+msgid "Record saved."
+msgstr "Der Datensatz wurde gespeichert."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Sie arbeiten nun an dem/den duplizierten Datensatz/Datensätzen."
+
+msgid "Are you sure to remove this record?"
+msgstr "Möchten Sie diesen Datensatz wirklich löschen?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Möchten Sie diese Datensätze wirklich löschen?"
+
+msgid "Records removed."
+msgstr "Die Datensätze wurden gelöscht."
+
+msgid "Records not removed."
+msgstr "Die Datensätze wurden nicht gelöscht."
+
+msgid "You have to select one record."
+msgstr "Ein Datensatz muss ausgewählt werden."
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Erstellt von:"
+
+msgid "Created at:"
+msgstr "Erstellt am:"
+
+msgid "Edited by:"
+msgstr "Bearbeitet von:"
+
+msgid "Edited at:"
+msgstr "Zuletzt bearbeitet am:"
+
+msgid "Model: "
+msgstr "Modell: "
+
+msgid "Add..."
+msgstr "Hinzufügen..."
+
+msgid "Preview"
+msgstr "Vorschau"
+
+msgid "Template"
+msgstr "Vorlage"
+
+msgid "Attachment (%1)"
+msgstr "Anhänge (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Notiz (%1/%2)"
+
+msgid " of "
+msgstr " von "
+
+msgid "Filters"
+msgstr "Filter"
+
+msgid "Clear Search"
+msgstr "Suchfeld leeren"
+
+msgid "Bookmarks"
+msgstr "Lesezeichen"
+
+msgid "Remove this bookmark"
+msgstr "Dieses Lesezeichen entfernen"
+
+msgid "Bookmark this filter"
+msgstr "Diesen Filter als Lesezeichen hinzufügen"
+
+msgid "Bookmark Name:"
+msgstr "Lesezeichenname:"
+
+msgid "Show active records"
+msgstr "Aktive Datensätze anzeigen"
+
+msgid "Show inactive records"
+msgstr "Inaktive Datensätze anzeigen"
+
+msgid "Find"
+msgstr "Suchen"
+
+msgid "From"
+msgstr "Von"
+
+msgid "To"
+msgstr "An"
+
+msgid "Open the calendar"
+msgstr "Kalender öffnen"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Erstellt von"
+
+msgid "Created at"
+msgstr "Erstellt am"
+
+msgid "Modified by"
+msgstr "Zuletzt verändert von"
+
+msgid "Modified at"
+msgstr "Zuletzt verändert am"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" ist erforderlich."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" liegt nicht im gültigen Wertebereich (Domain)."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "Die Werte von \"%1\" sind ungültig."
+
+msgid "Pre-validation"
+msgstr "Vorvalidierung"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Übersetzen"
+
+msgid "Edit"
+msgstr "Bearbeiten"
+
+msgid "Fuzzy"
+msgstr "Fuzzy"
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+"Der Datensatz muss gespeichert werden, bevor eine Übersetzung hinzugefügt "
+"werden kann."
+
+msgid "No other language available."
+msgstr "Keine weitere Sprache verfügbar."
+
+msgid "Show"
+msgstr "Anzeigen"
+
+msgid "Hide"
+msgstr "Ausblenden"
+
+msgid "Open the record"
+msgstr "Datensatz öffnen"
+
+msgid "Clear the field"
+msgstr "Feldinhalt löschen"
+
+msgid "Search a record"
+msgstr "Datensatz suchen"
+
+msgid "Add"
+msgstr "Hinzufügen"
+
+msgid "Remove"
+msgstr "Entfernen"
+
+msgid "Open"
+msgstr "Öffnen"
+
+msgid "Undelete"
+msgstr "Löschung rückgängig machen"
+
+msgid "Save As"
+msgstr "Speichern unter…"
+
+msgid "Save As..."
+msgstr "Speichern unter..."
+
+msgid "Select"
+msgstr "Auswählen"
+
+msgid "Select..."
+msgstr "Auswählen..."
+
+msgid "Clear"
+msgstr "Leeren"
+
+msgid "Choose a language"
+msgstr "Sprache auswählen"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Mehr"
+
+msgid "Today"
+msgstr "Heute"
+
+msgid "Month"
+msgstr "Monat"
+
+msgid "Week"
+msgstr "Woche"
+
+msgid "Day"
+msgstr "Tag"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Aktion wählen"
+
+msgid "No action defined."
+msgstr "Keine Aktion definiert."
+
+msgid "Attachments (%1)"
+msgstr "Anhänge (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notizen (%1)"
+
+msgid "Search %1"
+msgstr "Suche %1"
+
+msgid "Revision"
+msgstr "Bearbeitung"
+
+msgid "All Fields"
+msgstr "Alle Felder"
+
+msgid "Fields Selected"
+msgstr "Ausgewählte Felder"
+
+msgid "CSV Parameters"
+msgstr "CSV-Parameter"
+
+msgid "Delimiter:"
+msgstr "Feldtrenner:"
+
+msgid "Quote Char:"
+msgstr "Anführungszeichen:"
+
+msgid "CSV Import: %1"
+msgstr "CSV-Import: %1"
+
+msgid "Auto-Detect"
+msgstr "Automatische Erkennung"
+
+msgid "File to Import"
+msgstr "Zu importierende Datei"
+
+msgid "Encoding:"
+msgstr "Zeichenkodierung:"
+
+msgid "Lines to Skip:"
+msgstr "Zu überspringende Zeilen:"
+
+msgid "You must select an import file first."
+msgstr "Es muss zuerst eine Importdatei ausgewählt werden."
+
+msgid "An error occured in loading the file."
+msgstr "Fehler beim Öffnen der CSV-Datei."
+
+msgid "Error processing the file at field %1."
+msgstr "Fehler bei der Verarbeitung der Datei in Feld %1."
+
+msgid "Error"
+msgstr "Fehler"
+
+msgid "Error occured in loading the file"
+msgstr "Fehler beim Öffnen der CSV-Datei"
+
+msgid "CSV Export: %1"
+msgstr "CSV-Export: %1"
+
+msgid "Save Export"
+msgstr "Exportkonfiguration speichern"
+
+msgid "URL Export"
+msgstr "URL-Export"
+
+msgid "Delete Export"
+msgstr "Exportkonfiguration löschen"
+
+msgid "Predefined Exports"
+msgstr "Vordefinierte Exporte"
+
+msgid "Selected Records"
+msgstr "Ausgewählte Datensätze"
+
+msgid "Listed Records"
+msgstr "Angezeigte Datensätze"
+
+msgid "Export:"
+msgstr "Export:"
+
+msgid "Ignore search limit"
+msgstr "Suchlimit ignorieren"
+
+msgid "Use locale format"
+msgstr "Lokales Format verwenden"
+
+msgid "Add Field Names"
+msgstr "Feldnamen hinzufügen"
+
+msgid "%1 (string)"
+msgstr "%1 (string)"
+
+msgid "%1 (model name)"
+msgstr "%1 (Bezeichnung des Datensatzes)"
+
+msgid "%1 (record name)"
+msgstr "%1 (Bezeichnung des Datensatzes)"
+
+msgid "What is the name of this export?"
+msgstr "Wie soll der Name des Exports lauten?"
+
+msgid "Override %1 definition?"
+msgstr "Definition von %1 nicht berücksichtigen?"
+
+msgid "E-mail %1"
+msgstr "E-Mail %1"
+
+msgid "To:"
+msgstr "An:"
+
+msgid "Cc:"
+msgstr "Cc:"
+
+msgid "Bcc:"
+msgstr "Bcc:"
+
+msgid "Subject:"
+msgstr "Betreff:"
+
+msgid "Reports"
+msgstr "Berichte"
+
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-10-27 20:46+0000\n"
+"PO-Revision-Date: 2020-10-27 20:46+0000\n"
+"Last-Translator: Korbinian Preisler \n"
+"Language-Team: LANGUAGE \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.2.2\n"
+
+msgid "Attachments"
+msgstr "Anhänge"
+
+msgid "Files"
+msgstr "Dateien"
+
+msgid "Send"
+msgstr "Senden"
+
+msgid "Remove File"
+msgstr "Datei entfernen"
+
+msgid "Wizard"
+msgstr "Assistent"
+
+msgid "Translate view"
+msgstr "Aktuelle Sicht übersetzen"
diff --git a/sao/locale/es.po b/sao/locale/es.po
new file mode 100644
index 0000000000..f735b02afa
--- /dev/null
+++ b/sao/locale/es.po
@@ -0,0 +1,666 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Apr 06 2018 22:57:49 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Oct 09 2017 19:35:04 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "¿Está seguro que quiere salir?"
+
+msgid "Favorites"
+msgstr "Favoritos"
+
+msgid "Manage..."
+msgstr "Gestionar..."
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "Cerrar sesión"
+
+msgid "Action"
+msgstr "Acción"
+
+msgid "New"
+msgstr "Nuevo"
+
+msgid "Save"
+msgstr "Guardar"
+
+msgid "Switch"
+msgstr "Cambiar"
+
+msgid "Reload/Undo"
+msgstr "Recargar/Deshacer"
+
+msgid "Duplicate"
+msgstr "Duplicar"
+
+msgid "Delete"
+msgstr "Eliminar"
+
+msgid "Previous"
+msgstr "Anterior"
+
+msgid "Next"
+msgstr "Siguiente"
+
+msgid "Search"
+msgstr "Buscar"
+
+msgid "Close Tab"
+msgstr "Cerrar pestaña"
+
+msgid "Attachment"
+msgstr "Adjunto"
+
+msgid "Note"
+msgstr "Nota"
+
+msgid "Relate"
+msgstr "Relacionado"
+
+msgid "Print"
+msgstr "Imprimir"
+
+msgid "E-Mail"
+msgstr "Correo electrónico"
+
+msgid "Previous tab"
+msgstr "Pestaña anterior"
+
+msgid "Next tab"
+msgstr "Pestaña siguiente"
+
+msgid "Global search"
+msgstr "Búsqueda global"
+
+msgid "Show this help"
+msgstr "Muestra esta ayuda"
+
+msgid "Keyboard shortcuts"
+msgstr "Atajos de teclado"
+
+msgid "Close"
+msgstr "Cerrar"
+
+msgid "Global shortcuts"
+msgstr "Atajos globales"
+
+msgid "Tab shortcuts"
+msgstr "Atajos de las pestañas"
+
+msgid "Unable to reach the server."
+msgstr "No es posible conectar con el servidor."
+
+msgid "Too many requests. Try again later."
+msgstr "Demasiadas peticiones. Inténtelo de nuevo más tarde."
+
+msgid "Login"
+msgstr "Acceder"
+
+msgid "User name"
+msgstr "Nombre de usuario"
+
+msgid "Database"
+msgstr "Base de datos"
+
+msgid "Your selection:"
+msgstr "Su selección:"
+
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgid "OK"
+msgstr "Aceptar"
+
+msgid "Y"
+msgstr "A"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "S"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Por: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "a"
+
+msgid "Yes"
+msgstr "Sí"
+
+msgid "True"
+msgstr "Verdadero"
+
+msgid "t"
+msgstr "v"
+
+msgid "False"
+msgstr "Falso"
+
+msgid "Message: "
+msgstr "Mensaje: "
+
+msgid "Warning: "
+msgstr "Aviso: "
+
+msgid "Always ignore this warning."
+msgstr "Ignorar siempre esta advertencia."
+
+msgid "Do you want to proceed?"
+msgstr "¿Desea continuar?"
+
+msgid "No"
+msgstr "No"
+
+msgid "Confirmation: "
+msgstr "Confirmación: "
+
+msgid "Concurrency Exception"
+msgstr "Excepción de concurrencia"
+
+msgid "Write Concurrency Warning: "
+msgstr "Aviso de concurrencia de escritura: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Este registro ha sido modificado mientras lo editaba."
+
+msgid "Choose:"
+msgstr "Elija:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Cancelar\" para no guardar;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Comparar\" para ver la versión modificada;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Guardar de todas formas\" para guardar sus cambios."
+
+msgid "Compare"
+msgstr "Comparar"
+
+msgid "Compare: %1"
+msgstr "Comparar: %1"
+
+msgid "Write Anyway"
+msgstr "Guardar de todas formas"
+
+msgid "Application Error"
+msgstr "Error de aplicación"
+
+msgid "Report Bug"
+msgstr "Informar del error"
+
+msgid "Processing"
+msgstr "Procesando"
+
+msgid "Search..."
+msgstr "Buscar…"
+
+msgid "Create..."
+msgstr "Crear..."
+
+msgid "Download"
+msgstr "Descargar"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Tipo de letra"
+
+msgid "Size"
+msgstr "Tamaño"
+
+msgid "Switch view"
+msgstr "Cambiar de vista"
+
+msgid "Previous Record"
+msgstr "Registro anterior"
+
+msgid "Next Record"
+msgstr "Registro siguiente"
+
+msgid "Create a new record"
+msgstr "Crear un nuevo registro"
+
+msgid "Save this record"
+msgstr "Guardar este registro"
+
+msgid "Reload"
+msgstr "Recargar"
+
+msgid "View Logs..."
+msgstr "Ver registro..."
+
+msgid "Show revisions..."
+msgstr "Mostrar revisiones..."
+
+msgid "Add an attachment to the record"
+msgstr "Añadir un adjunto al registro"
+
+msgid "Add a note to the record"
+msgstr "Añadir una nota al registro"
+
+msgid "E-Mail..."
+msgstr "Correo electrónico..."
+
+msgid "Send an e-mail using the record"
+msgstr "Enviar un correo electrónico usando el registro"
+
+msgid "Export"
+msgstr "Exportar"
+
+msgid "Import"
+msgstr "Importar"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"La acción seleccionada requiere cerrar todas las pestañas.\n"
+"¿Desea continuar?"
+
+msgid "Launch action"
+msgstr "Ejecutar acción"
+
+msgid "Open related records"
+msgstr "Abrir registros relacionados"
+
+msgid "Print report"
+msgstr "Imprimir informe"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Este registro ha sido modificado.\n"
+"¿Desea guardarlo?"
+
+msgid "Record saved."
+msgstr "Registro guardado."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Ahora está trabajando en el/los registro(s) duplicado(s)."
+
+msgid "Are you sure to remove this record?"
+msgstr "¿Está seguro que quiere eliminar este registro?"
+
+msgid "Are you sure to remove those records?"
+msgstr "¿Está seguro que quiere eliminar estos registros?"
+
+msgid "Records removed."
+msgstr "Registros eliminados."
+
+msgid "Records not removed."
+msgstr "Los registros no se han eliminado."
+
+msgid "You have to select one record."
+msgstr "Debe elegir un registro."
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Creado por:"
+
+msgid "Created at:"
+msgstr "Creado el:"
+
+msgid "Edited by:"
+msgstr "Editado por:"
+
+msgid "Edited at:"
+msgstr "Editado el:"
+
+msgid "Model: "
+msgstr "Modelo: "
+
+msgid "Add..."
+msgstr "Añadir..."
+
+msgid "Preview"
+msgstr "Previsualizar"
+
+msgid "Template"
+msgstr "Plantilla"
+
+msgid "Attachment (%1)"
+msgstr "Adjuntos (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Nota (%1/%2)"
+
+msgid " of "
+msgstr " de "
+
+msgid "Filters"
+msgstr "Filtros"
+
+msgid "Clear Search"
+msgstr "Limpiar la búsqueda"
+
+msgid "Bookmarks"
+msgstr "Búsquedas favoritas"
+
+msgid "Remove this bookmark"
+msgstr "Eliminar de las búsquedas favoritas"
+
+msgid "Bookmark this filter"
+msgstr "Guardar como búsqueda favorita"
+
+msgid "Bookmark Name:"
+msgstr "Nombre de la búsqueda favorita:"
+
+msgid "Show active records"
+msgstr "Mostrar registros activos"
+
+msgid "Show inactive records"
+msgstr "Mostrar registros inactivos"
+
+msgid "Find"
+msgstr "Buscar"
+
+msgid "From"
+msgstr "De"
+
+msgid "To"
+msgstr "A"
+
+msgid "Open the calendar"
+msgstr "Abrir el calendario"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Creado por"
+
+msgid "Created at"
+msgstr "Creado el"
+
+msgid "Modified by"
+msgstr "Modifcador por"
+
+msgid "Modified at"
+msgstr "Modificado el"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" es obligatorio."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" no es válido según su dominio."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "Los valores de \"%1\" no son válidos."
+
+msgid "Pre-validation"
+msgstr "Prevalidación"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Traducir"
+
+msgid "Edit"
+msgstr "Editar"
+
+msgid "Fuzzy"
+msgstr "Dudoso"
+
+msgid "You need to save the record before adding translations."
+msgstr "Debe guardar el registro antes de añadir traducciones."
+
+msgid "No other language available."
+msgstr "No hay otro idioma disponible."
+
+msgid "Show"
+msgstr "Mostrar"
+
+msgid "Hide"
+msgstr "Ocultar"
+
+msgid "Open the record"
+msgstr "Abrir el registro"
+
+msgid "Clear the field"
+msgstr "Vacíar el campo"
+
+msgid "Search a record"
+msgstr "Buscar un registro"
+
+msgid "Add"
+msgstr "Añadir"
+
+msgid "Remove"
+msgstr "Eliminar"
+
+msgid "Open"
+msgstr "Abrir"
+
+msgid "Undelete"
+msgstr "Recuperar"
+
+msgid "Save As"
+msgstr "Guardar como"
+
+msgid "Save As..."
+msgstr "Guardar como..."
+
+msgid "Select"
+msgstr "Seleccionar"
+
+msgid "Select..."
+msgstr "Seleccionar…"
+
+msgid "Clear"
+msgstr "Limpiar"
+
+msgid "Choose a language"
+msgstr "Escoja un lenguaje"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Más"
+
+msgid "Today"
+msgstr "Hoy"
+
+msgid "Month"
+msgstr "Mes"
+
+msgid "Week"
+msgstr "Semana"
+
+msgid "Day"
+msgstr "Día"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Seleccione su acción"
+
+msgid "No action defined."
+msgstr "No se ha definido ninguna acción."
+
+msgid "Attachments (%1)"
+msgstr "Adjuntos (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notas (%1)"
+
+msgid "Search %1"
+msgstr "Buscar %1"
+
+msgid "Revision"
+msgstr "Revisión"
+
+msgid "All Fields"
+msgstr "Todos los campos"
+
+msgid "Fields Selected"
+msgstr "Campos selecionados"
+
+msgid "CSV Parameters"
+msgstr "Parámetros CSV"
+
+msgid "Delimiter:"
+msgstr "Separador:"
+
+msgid "Quote Char:"
+msgstr "Delimitador del texto:"
+
+msgid "CSV Import: %1"
+msgstr "Importación CSV: %1"
+
+msgid "Auto-Detect"
+msgstr "Detectar automáticamente"
+
+msgid "File to Import"
+msgstr "Fichero a importar"
+
+msgid "Encoding:"
+msgstr "Codificación:"
+
+msgid "Lines to Skip:"
+msgstr "Líneas a omitir:"
+
+msgid "You must select an import file first."
+msgstr "Primero debe elegir un archivo a importar."
+
+msgid "An error occured in loading the file."
+msgstr "Ha ocurrido un error al cargar el fichero."
+
+msgid "Error processing the file at field %1."
+msgstr "Error procesando el fichero en el campo %1."
+
+msgid "Error"
+msgstr "Error"
+
+msgid "Error occured in loading the file"
+msgstr "Ha ocurrido un al cargar el fichero"
+
+msgid "CSV Export: %1"
+msgstr "Exportación CSV: %1"
+
+msgid "Save Export"
+msgstr "Guardar exportación"
+
+msgid "URL Export"
+msgstr "URL exportación"
+
+msgid "Delete Export"
+msgstr "Eliminar exportación"
+
+msgid "Predefined Exports"
+msgstr "Exportaciones predefinidas"
+
+msgid "Selected Records"
+msgstr "Registros seleccionados"
+
+msgid "Listed Records"
+msgstr "Registros listados"
+
+msgid "Export:"
+msgstr "Exportar:"
+
+msgid "Ignore search limit"
+msgstr "Ignorar límite de búsqueda"
+
+msgid "Use locale format"
+msgstr "Usar formato local"
+
+msgid "Add Field Names"
+msgstr "Añadir nombres de los campos"
+
+msgid "%1 (string)"
+msgstr "%1 (cadena)"
+
+msgid "%1 (model name)"
+msgstr "%1 (nombre del modelo)"
+
+msgid "%1 (record name)"
+msgstr "%1 (nombre del registro)"
+
+msgid "What is the name of this export?"
+msgstr "¿Cuál es el nombre de esta exportación?"
+
+msgid "Override %1 definition?"
+msgstr "Queréis sobrescribir la definición de %1?"
+
+msgid "E-mail %1"
+msgstr "Correo electrónico %1"
+
+msgid "To:"
+msgstr "A:"
+
+msgid "Cc:"
+msgstr "Cc:"
+
+msgid "Bcc:"
+msgstr "CCo:"
+
+msgid "Subject:"
+msgstr "Asunto:"
+
+msgid "Reports"
+msgstr "Informes"
+
+msgid "Attachments"
+msgstr "Adjuntos"
+
+msgid "Files"
+msgstr "Archivos"
+
+msgid "Send"
+msgstr "Enviar"
+
+msgid "Remove File"
+msgstr "Eliminar archivo"
+
+msgid "Wizard"
+msgstr "Asistente"
+
+msgid "Translate view"
+msgstr "Traducir vista"
diff --git a/sao/locale/es_419.po b/sao/locale/es_419.po
new file mode 100644
index 0000000000..7c20f559cd
--- /dev/null
+++ b/sao/locale/es_419.po
@@ -0,0 +1,669 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+msgid ", "
+msgstr ""
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "..."
+msgstr ""
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+#, fuzzy
+msgid "Created by:"
+msgstr "Crear..."
+
+#, fuzzy
+msgid "Created at:"
+msgstr "Fecha creación:"
+
+#, fuzzy
+msgid "Edited by:"
+msgstr "Crear..."
+
+#, fuzzy
+msgid "Edited at:"
+msgstr "Última fecha de modificación:"
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+#, fuzzy
+msgid "Attachment (%1)"
+msgstr "Adjuntos (%1)"
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+#, fuzzy
+msgid "Created by"
+msgstr "Crear..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "Fecha creación:"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "Última fecha de modificación:"
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ""
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr "Revisión"
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+#, fuzzy
+msgid "Open the record"
+msgstr "Abrir registro "
+
+msgid "Clear the field"
+msgstr ""
+
+#, fuzzy
+msgid "Search a record"
+msgstr "Buscar registro "
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+msgid ": "
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+#, fuzzy
+msgid "Attachments"
+msgstr "Adjuntos (%1)"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/et.po b/sao/locale/et.po
new file mode 100644
index 0000000000..095bfcbab7
--- /dev/null
+++ b/sao/locale/et.po
@@ -0,0 +1,681 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "Kas soovid lahkuda?"
+
+msgid "Favorites"
+msgstr "Lemmikud"
+
+msgid "Manage..."
+msgstr "Halda..."
+
+msgid "Logout"
+msgstr "Logi välja"
+
+msgid "Action"
+msgstr "Toiming"
+
+msgid "New"
+msgstr "Uus"
+
+msgid "Save"
+msgstr "Salvesta"
+
+msgid "Switch"
+msgstr "Vaheta"
+
+msgid "Reload/Undo"
+msgstr "Laadi uuesti/Taasta"
+
+msgid "Duplicate"
+msgstr "Kopeeri"
+
+msgid "Delete"
+msgstr "Kustuta"
+
+msgid "Previous"
+msgstr "Eelmine"
+
+msgid "Next"
+msgstr "Järgmine"
+
+msgid "Search"
+msgstr "Otsi"
+
+msgid "Close Tab"
+msgstr "Sulge vaheleht"
+
+msgid "Attachment"
+msgstr "Manus"
+
+msgid "Note"
+msgstr "Märkus"
+
+msgid "Relate"
+msgstr "Seosta"
+
+msgid "Print"
+msgstr "Prindi"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr "Eelmine vaheleht"
+
+msgid "Next tab"
+msgstr "Järgmine vaheleht"
+
+msgid "Global search"
+msgstr "Üldine otsing"
+
+msgid "Show this help"
+msgstr "Näita abiinfot"
+
+msgid "Keyboard shortcuts"
+msgstr "Kiirklahvid"
+
+msgid "Close"
+msgstr "Sulge"
+
+msgid "Global shortcuts"
+msgstr "Globaalsed otseteed"
+
+msgid "Tab shortcuts"
+msgstr "Vahelehe otseteed"
+
+msgid "Unable to reach the server."
+msgstr "Serverit ei leita."
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "Logi sisse"
+
+msgid "User name"
+msgstr "Kasutaja nimi"
+
+msgid "Database"
+msgstr "Andmebaas"
+
+msgid "Your selection:"
+msgstr "Valik:"
+
+msgid "Cancel"
+msgstr "Tühista"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "A"
+
+msgid "M"
+msgstr "K"
+
+msgid "w"
+msgstr "n"
+
+msgid "d"
+msgstr "p"
+
+msgid "h"
+msgstr "t"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr ""
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr "Jah"
+
+msgid "True"
+msgstr "Tõene"
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr "Väär"
+
+msgid "Message: "
+msgstr "Teade: "
+
+msgid "Warning: "
+msgstr "Hoiatus: "
+
+msgid "Always ignore this warning."
+msgstr "Eira alati seda hoiatust."
+
+msgid "Do you want to proceed?"
+msgstr "Kas soovid jätkata?"
+
+msgid "No"
+msgstr "Ei"
+
+msgid "Confirmation: "
+msgstr "Kinnitus: "
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr "Kirje on muutunud viimasest salvestamisest alates."
+
+msgid "Choose:"
+msgstr "Vali:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Tühista\", et katkestada salvestamine;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Võrdle\", et vaadata muudetud versiooni;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Salvesta ikkagi\", et salvestada viimased muudatused."
+
+msgid "Compare"
+msgstr "Võrdle"
+
+#, fuzzy
+msgid "Compare: %1"
+msgstr "Võrdle"
+
+msgid "Write Anyway"
+msgstr "Salvesta ikkagi"
+
+msgid "Application Error"
+msgstr "Rakenduse viga"
+
+msgid "Report Bug"
+msgstr "Vearaport"
+
+#, fuzzy
+msgid "Processing"
+msgstr "Töötlemine..."
+
+msgid "Search..."
+msgstr "Otsi..."
+
+msgid "Create..."
+msgstr "Loo..."
+
+msgid "Download"
+msgstr "Allalaadimine"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Šrift"
+
+msgid "Size"
+msgstr "Suurus"
+
+msgid "Switch view"
+msgstr "Vaheta vaadet"
+
+msgid "Previous Record"
+msgstr "Eelmine kirje"
+
+msgid "Next Record"
+msgstr "Järgmine kirje"
+
+msgid "Create a new record"
+msgstr "Loo uus kirje"
+
+msgid "Save this record"
+msgstr "Salvesta kirje"
+
+msgid "Reload"
+msgstr "Taaslae"
+
+msgid "View Logs..."
+msgstr "Vaata logisid..."
+
+msgid "Show revisions..."
+msgstr "Näita muudatusi..."
+
+msgid "Add an attachment to the record"
+msgstr "Lisa manus"
+
+msgid "Add a note to the record"
+msgstr "Lisa märkus"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "Lisa märkus"
+
+msgid "Export"
+msgstr "Eksport"
+
+msgid "Import"
+msgstr "Import"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Järgmine toiming sulgeb kõik vahelehed.\n"
+"Kas soovid jätkata?"
+
+msgid "Launch action"
+msgstr "Käivita toiming"
+
+msgid "Open related records"
+msgstr "Ava seotud kirjed"
+
+msgid "Print report"
+msgstr "Prindi aruanne"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Seda kirjet on muudetud,\n"
+"kas soovid salvestada?"
+
+msgid "Record saved."
+msgstr "Kirje salvestatud."
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr "Kas soovid selle kirje eemaldada?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Kas soovid need kirjed eemaldada?"
+
+msgid "Records removed."
+msgstr "Kirjed eemaldatud."
+
+msgid "Records not removed."
+msgstr "Kirjeid ei eemaldatud."
+
+msgid "You have to select one record."
+msgstr "Aktiveeri üks kirje."
+
+msgid "ID:"
+msgstr "ID"
+
+#, fuzzy
+msgid "Created by:"
+msgstr "Loo..."
+
+#, fuzzy
+msgid "Created at:"
+msgstr "Loomise kuupäev"
+
+#, fuzzy
+msgid "Edited by:"
+msgstr "Loo..."
+
+#, fuzzy
+msgid "Edited at:"
+msgstr "Muutmise kuupäev"
+
+msgid "Model: "
+msgstr "Mudel: "
+
+msgid "Add..."
+msgstr "Lisa..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "Eelmine"
+
+#, fuzzy
+msgid "Template"
+msgstr "Seosta"
+
+msgid "Attachment (%1)"
+msgstr "Manus (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Märkus (%1/%2)"
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr "Filtrid"
+
+msgid "Clear Search"
+msgstr "Tühjenda otsing"
+
+msgid "Bookmarks"
+msgstr "Järjehoidjad"
+
+msgid "Remove this bookmark"
+msgstr "Eemalda järjehoidja"
+
+msgid "Bookmark this filter"
+msgstr "Lisa filter järjehoidjatesse"
+
+msgid "Bookmark Name:"
+msgstr "Järjehoidja nimi:"
+
+msgid "Show active records"
+msgstr "Näita aktiivseid kirjeid"
+
+msgid "Show inactive records"
+msgstr "Näita mitteaktiivseid kirjeid"
+
+msgid "Find"
+msgstr "Leia"
+
+msgid "From"
+msgstr "Saatja"
+
+msgid "To"
+msgstr "Saaja"
+
+msgid "Open the calendar"
+msgstr "Ava kalender"
+
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgid "Created by"
+msgstr "Loo..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "Loomise kuupäev"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "Muutmise kuupäev"
+
+#, fuzzy
+msgid "\"%1\" is required."
+msgstr "\"%1\" on nõutud"
+
+#, fuzzy
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" ei ole kehtiv selle domeeni jaoks"
+
+#, fuzzy
+msgid "The values of \"%1\" are not valid."
+msgstr "Väärtused \"%1\" ei ole kehtivad"
+
+msgid "Pre-validation"
+msgstr "Eelkontroll"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Tõlgi"
+
+msgid "Edit"
+msgstr "Muuda"
+
+msgid "Fuzzy"
+msgstr "Hägune"
+
+msgid "You need to save the record before adding translations."
+msgstr "Kirje tuleb salvestada enne tõlke lisamist."
+
+msgid "No other language available."
+msgstr "Teisi keeli pole saadaval."
+
+msgid "Show"
+msgstr "Näita"
+
+msgid "Hide"
+msgstr "Peida"
+
+msgid "Open the record"
+msgstr "Ava kirje"
+
+msgid "Clear the field"
+msgstr "Tühjenda väli"
+
+msgid "Search a record"
+msgstr "Otsi kirjet"
+
+msgid "Add"
+msgstr "Lisa"
+
+msgid "Remove"
+msgstr "Eemalda"
+
+msgid "Open"
+msgstr "Ava"
+
+msgid "Undelete"
+msgstr "Taasta"
+
+msgid "Save As"
+msgstr "Salvesta nimega"
+
+msgid "Save As..."
+msgstr "Salvesta nimega..."
+
+msgid "Select"
+msgstr "Vali"
+
+msgid "Select..."
+msgstr "Vali..."
+
+msgid "Clear"
+msgstr "Tühjenda"
+
+msgid "Choose a language"
+msgstr "Vali keel"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Lisaks"
+
+msgid "Today"
+msgstr "Täna"
+
+msgid "Month"
+msgstr "Kuu"
+
+msgid "Week"
+msgstr "Nädal"
+
+msgid "Day"
+msgstr "Päev"
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Vali toiming"
+
+msgid "No action defined."
+msgstr "Toiming kirjeldamata."
+
+msgid "Attachments (%1)"
+msgstr "Manused (%1)"
+
+msgid "Notes (%1)"
+msgstr "Märkused (%1)"
+
+msgid "Search %1"
+msgstr "Otsi %1"
+
+msgid "Revision"
+msgstr "Läbivaatus"
+
+msgid "All Fields"
+msgstr "Kõik väljad"
+
+msgid "Fields Selected"
+msgstr "Valitud väljad"
+
+msgid "CSV Parameters"
+msgstr "CSV parameetrid"
+
+msgid "Delimiter:"
+msgstr "Eraldaja:"
+
+msgid "Quote Char:"
+msgstr "Teksti eraldaja:"
+
+msgid "CSV Import: %1"
+msgstr "CSV import: %1"
+
+msgid "Auto-Detect"
+msgstr "Automaattuvastus"
+
+msgid "File to Import"
+msgstr "Impordi fail"
+
+msgid "Encoding:"
+msgstr "Kodeering:"
+
+msgid "Lines to Skip:"
+msgstr "Jäta vahele read:"
+
+#, fuzzy
+msgid "You must select an import file first."
+msgstr "Vali imporditav fail"
+
+#, fuzzy
+msgid "An error occured in loading the file."
+msgstr "Faili laadimisel tekkis viga"
+
+msgid "Error processing the file at field %1."
+msgstr "Faili töötlemisel tekkis väljal %1 viga."
+
+msgid "Error"
+msgstr "Viga"
+
+msgid "Error occured in loading the file"
+msgstr "Faili laadimisel tekkis viga"
+
+msgid "CSV Export: %1"
+msgstr "CSV eksport: %1"
+
+msgid "Save Export"
+msgstr "Salvesta eksport"
+
+#, fuzzy
+msgid "URL Export"
+msgstr "Eksport"
+
+msgid "Delete Export"
+msgstr "Kustuta eksport"
+
+msgid "Predefined Exports"
+msgstr "Salvestatud ekspordid"
+
+#, fuzzy
+msgid "Selected Records"
+msgstr "Järgmine kirje"
+
+#, fuzzy
+msgid "Listed Records"
+msgstr "Järgmine kirje"
+
+#, fuzzy
+msgid "Export:"
+msgstr "Eksport"
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr "Kasuta piirkonna formaati"
+
+msgid "Add Field Names"
+msgstr "Lisa välja nimed"
+
+msgid "%1 (string)"
+msgstr "%1 (tekst)"
+
+msgid "%1 (model name)"
+msgstr "%1 (mudeli nimi)"
+
+msgid "%1 (record name)"
+msgstr "%1 (kirje nimi)"
+
+msgid "What is the name of this export?"
+msgstr "Ekspordi nimi?"
+
+msgid "Override %1 definition?"
+msgstr "Kirjutada üle 1% kirjeldus?"
+
+msgid "E-mail %1"
+msgstr ""
+
+#, fuzzy
+msgid "To:"
+msgstr "Saaja"
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "Vearaport"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "Manus"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Eemalda"
+
+msgid "Wizard"
+msgstr "Nõustaja"
+
+msgid "Translate view"
+msgstr "Tõlgi vaade"
diff --git a/sao/locale/fa.po b/sao/locale/fa.po
new file mode 100644
index 0000000000..3398382a15
--- /dev/null
+++ b/sao/locale/fa.po
@@ -0,0 +1,691 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Oct 09 2017 19:35:04 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "برای ترک برنامه، آیا مطمئن هستید؟"
+
+msgid "Favorites"
+msgstr "علاقه مندی ها"
+
+msgid "Manage..."
+msgstr "مدیریت..."
+
+msgid "Logout"
+msgstr "خروج"
+
+msgid "Action"
+msgstr "اقدام"
+
+msgid "New"
+msgstr "جدید"
+
+msgid "Save"
+msgstr "ذخیره"
+
+msgid "Switch"
+msgstr "سوئیچ"
+
+msgid "Reload/Undo"
+msgstr "بارگیری مجدد / واگرد"
+
+msgid "Duplicate"
+msgstr "تکثیر"
+
+msgid "Delete"
+msgstr "حذف"
+
+msgid "Previous"
+msgstr "قبلی"
+
+msgid "Next"
+msgstr "بعدی"
+
+msgid "Search"
+msgstr "جستجو"
+
+msgid "Close Tab"
+msgstr "بستن برگه"
+
+msgid "Attachment"
+msgstr "پیوست"
+
+msgid "Note"
+msgstr "توجه"
+
+msgid "Relate"
+msgstr "مربوط"
+
+msgid "Print"
+msgstr "چاپ"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr "برگه قبلی"
+
+msgid "Next tab"
+msgstr "برگه بعدی"
+
+msgid "Global search"
+msgstr "جستجوی سراسری"
+
+msgid "Show this help"
+msgstr "نمایش این راهنما"
+
+msgid "Keyboard shortcuts"
+msgstr "میانبرهای صفحه کلید"
+
+msgid "Close"
+msgstr "بسته"
+
+msgid "Global shortcuts"
+msgstr "کلید های میانبر سراسری"
+
+msgid "Tab shortcuts"
+msgstr "کلید های میانبر برگه"
+
+msgid "Unable to reach the server."
+msgstr "امکان دسترسی به سرور وجود ندارد."
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "ورود"
+
+msgid "User name"
+msgstr "نام کاربری"
+
+msgid "Database"
+msgstr "پایگاه داده"
+
+msgid "Your selection:"
+msgstr "انتخاب شما :"
+
+msgid "Cancel"
+msgstr "انصراف"
+
+msgid "OK"
+msgstr "قبول"
+
+msgid "Y"
+msgstr "Y"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "w"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "توسط : "
+
+msgid ", "
+msgstr "، "
+
+msgid "y"
+msgstr "y"
+
+msgid "Yes"
+msgstr "بله"
+
+msgid "True"
+msgstr "درست"
+
+msgid "t"
+msgstr "t"
+
+msgid "False"
+msgstr "غلط"
+
+msgid "Message: "
+msgstr "پیام : "
+
+msgid "Warning: "
+msgstr "هشدار: "
+
+msgid "Always ignore this warning."
+msgstr "همیشه این هشدار را نادیده بگیرید."
+
+msgid "Do you want to proceed?"
+msgstr "آیا می خواهید پردازش شود؟"
+
+msgid "No"
+msgstr "خیر"
+
+msgid "Confirmation: "
+msgstr "تأییدیه : "
+
+msgid "Concurrency Exception"
+msgstr "همزمانی استثنا"
+
+msgid "Write Concurrency Warning: "
+msgstr "نوشتن همزمانی هشدار: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "این پرونده در حالی که شما در حال ویرایش آن بودید، اصلاح شد."
+
+msgid "Choose:"
+msgstr "انتخاب :"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"لغو\" برای انصراف ذخیره کردن ؛"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"مقایسه\" برای دیدن نسخه اصلاح شده ؛"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"نوشتن بهرحال\" برای ذخیره نسخه فعلی خود."
+
+msgid "Compare"
+msgstr "مقایسه"
+
+#, fuzzy
+msgid "Compare: %1"
+msgstr "مقایسه"
+
+msgid "Write Anyway"
+msgstr "نوشتن بهرحال"
+
+msgid "Application Error"
+msgstr "خطای برنامه"
+
+msgid "Report Bug"
+msgstr "گزارش باگ"
+
+#, fuzzy
+msgid "Processing"
+msgstr "در حال پردازش..."
+
+msgid "Search..."
+msgstr "جستجو..."
+
+msgid "Create..."
+msgstr "ایجاد..."
+
+msgid "Download"
+msgstr "دانلود"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "فونت"
+
+msgid "Size"
+msgstr "اندازه"
+
+msgid "Switch view"
+msgstr "تعویض نمایه"
+
+msgid "Previous Record"
+msgstr "پرونده قبلی"
+
+msgid "Next Record"
+msgstr "پرونده بعدی"
+
+msgid "Create a new record"
+msgstr "ایجاد یک پرونده جدید"
+
+msgid "Save this record"
+msgstr "ذخیره این پرونده"
+
+msgid "Reload"
+msgstr "بارگذاری مجدد"
+
+msgid "View Logs..."
+msgstr "مشاهده گزارشات..."
+
+msgid "Show revisions..."
+msgstr "مشاهده تجدیدنظر..."
+
+msgid "Add an attachment to the record"
+msgstr "پیوست را به پرونده اضافه کنید"
+
+msgid "Add a note to the record"
+msgstr "یک یادداشت به پرونده اضافه کنید"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "یک یادداشت به پرونده اضافه کنید"
+
+msgid "Export"
+msgstr "صادر کردن"
+
+msgid "Import"
+msgstr "وارد كردن"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"اقدام زیر نیاز به بستن همه برگه ها دارد.\n"
+"می خواهید ادامه دهید؟"
+
+msgid "Launch action"
+msgstr "انجام اقدام"
+
+msgid "Open related records"
+msgstr "بازکردن پرونده های مربوطه"
+
+msgid "Print report"
+msgstr "چاپ گزارش"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"این پرونده هنوز اصلاح نشده است.\n"
+"میخواهید آن را ذخیره کنید ؟"
+
+msgid "Record saved."
+msgstr "پرونده ذخیره شد."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "هم اکنون کار بر روی پرونده(ها) تکثیر شده انجام می شود."
+
+msgid "Are you sure to remove this record?"
+msgstr "برای حذف این پرونده اطمینان کامل دارید؟"
+
+msgid "Are you sure to remove those records?"
+msgstr "برای حذف این پرونده ها اطمینان کامل دارید؟"
+
+msgid "Records removed."
+msgstr "پرونده ها حذف شدند."
+
+msgid "Records not removed."
+msgstr "پرونده ها حذف نشدند."
+
+msgid "You have to select one record."
+msgstr "شما باید یک پرونده را انتخاب کنید."
+
+msgid "ID:"
+msgstr "شناسه :"
+
+#, fuzzy
+msgid "Created by:"
+msgstr "ایجاد..."
+
+#, fuzzy
+msgid "Created at:"
+msgstr "تاریخ ایجاد"
+
+#, fuzzy
+msgid "Edited by:"
+msgstr "ایجاد..."
+
+#, fuzzy
+msgid "Edited at:"
+msgstr "تاریخ اصلاح"
+
+msgid "Model: "
+msgstr "مدل : "
+
+msgid "Add..."
+msgstr "افزودن..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "قبلی"
+
+#, fuzzy
+msgid "Template"
+msgstr "مربوط"
+
+#, fuzzy
+msgid "Attachment (%1)"
+msgstr "پیوست ها(1%)"
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr " از "
+
+msgid "Filters"
+msgstr "فیلترها"
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr "بوک مارک ها"
+
+msgid "Remove this bookmark"
+msgstr "حذف این بوک مارک"
+
+msgid "Bookmark this filter"
+msgstr "بوک مارک این فیلتر"
+
+msgid "Bookmark Name:"
+msgstr "نام بوک مارک :"
+
+msgid "Show active records"
+msgstr "نمایش پرونده های فعال"
+
+msgid "Show inactive records"
+msgstr "نمایش پرونده های غیرفعال"
+
+msgid "Find"
+msgstr "یافتن"
+
+msgid "From"
+msgstr "از"
+
+msgid "To"
+msgstr "به"
+
+msgid "Open the calendar"
+msgstr "بازکردن تقویم"
+
+msgid "ID"
+msgstr "شناسه"
+
+#, fuzzy
+msgid "Created by"
+msgstr "ایجاد..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "تاریخ ایجاد"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "تاریخ اصلاح"
+
+#, fuzzy
+msgid "\"%1\" is required."
+msgstr "\"%1\" مورد نیاز است"
+
+#, fuzzy
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" با توجه به دامنه آن معتبر نیست"
+
+#, fuzzy
+msgid "The values of \"%1\" are not valid."
+msgstr "مقادیر \"%1\" معتبر نیستند"
+
+msgid "Pre-validation"
+msgstr "پیش-اعتبارسنجی"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "ترجمه"
+
+msgid "Edit"
+msgstr "ویرایش"
+
+msgid "Fuzzy"
+msgstr "نامانوس"
+
+msgid "You need to save the record before adding translations."
+msgstr "قبل از اضافه کردن ترجمه ها شما باید پرونده را ذخیره کنید."
+
+msgid "No other language available."
+msgstr "هیچ زبان دیگری در دسترس نیست."
+
+msgid "Show"
+msgstr "نمایش"
+
+msgid "Hide"
+msgstr "مخفی"
+
+#, fuzzy
+msgid "Open the record"
+msgstr "بازکردن پرونده "
+
+msgid "Clear the field"
+msgstr ""
+
+#, fuzzy
+msgid "Search a record"
+msgstr "جستجوی یک پرونده "
+
+msgid "Add"
+msgstr "افزودن"
+
+msgid "Remove"
+msgstr "حذف"
+
+msgid "Open"
+msgstr "باز"
+
+msgid "Undelete"
+msgstr "بازگردانی"
+
+msgid "Save As"
+msgstr "ذخیره با نام"
+
+msgid "Save As..."
+msgstr "ذخیره با نام..."
+
+msgid "Select"
+msgstr "انتخاب"
+
+msgid "Select..."
+msgstr "انتخاب..."
+
+msgid "Clear"
+msgstr "پاک کردن"
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "بیشتر"
+
+msgid "Today"
+msgstr "امروز"
+
+msgid "Month"
+msgstr "ماه"
+
+msgid "Week"
+msgstr "هفته"
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "انتخاب اقدام شما"
+
+msgid "No action defined."
+msgstr "اقدامی تعریف نشده."
+
+msgid "Attachments (%1)"
+msgstr "پیوست ها(1%)"
+
+msgid "Notes (%1)"
+msgstr "یادداشت ها(1%)"
+
+msgid "Search %1"
+msgstr "جستجو(1%)"
+
+msgid "Revision"
+msgstr "تجدید نظر"
+
+msgid "All Fields"
+msgstr "همه فیلد ها"
+
+msgid "Fields Selected"
+msgstr "فیلد های انتخاب شده"
+
+msgid "CSV Parameters"
+msgstr "مولفه های CSV"
+
+msgid "Delimiter:"
+msgstr "جدا کننده :"
+
+msgid "Quote Char:"
+msgstr "کاراکترنقل قول :"
+
+msgid "CSV Import: %1"
+msgstr "وارد کردن (CSV) : 1%"
+
+msgid "Auto-Detect"
+msgstr "شناسایی - خودکار"
+
+msgid "File to Import"
+msgstr "فایل برای واردکردن"
+
+msgid "Encoding:"
+msgstr "رمزگذاری :"
+
+msgid "Lines to Skip:"
+msgstr "چشم پوشی ازخطوط:"
+
+#, fuzzy
+msgid "You must select an import file first."
+msgstr "شما باید ابتدا فایل ورودی را انتخاب کنید"
+
+#, fuzzy
+msgid "An error occured in loading the file."
+msgstr "خطا هنگام بارگیری فایل"
+
+msgid "Error processing the file at field %1."
+msgstr "خطای پردازش فایل واقع در فیلد : \"%1\"."
+
+msgid "Error"
+msgstr "خطا"
+
+msgid "Error occured in loading the file"
+msgstr "خطا هنگام بارگیری فایل"
+
+msgid "CSV Export: %1"
+msgstr "صادرکردن (CSV) : 1%"
+
+msgid "Save Export"
+msgstr "ذخیره استخراج"
+
+#, fuzzy
+msgid "URL Export"
+msgstr "صادر کردن"
+
+msgid "Delete Export"
+msgstr "حذف استخراج"
+
+msgid "Predefined Exports"
+msgstr "استخراج از پیش تعریف شده"
+
+#, fuzzy
+msgid "Selected Records"
+msgstr "پرونده بعدی"
+
+#, fuzzy
+msgid "Listed Records"
+msgstr "پرونده بعدی"
+
+#, fuzzy
+msgid "Export:"
+msgstr "صادر کردن"
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr "اضافه کردن نام های فیلد"
+
+msgid "%1 (string)"
+msgstr "\"1%\" (متنی)"
+
+msgid "%1 (model name)"
+msgstr "\"1%\" (نام مدل)"
+
+msgid "%1 (record name)"
+msgstr "\"1%\" (نام پرونده)"
+
+msgid "What is the name of this export?"
+msgstr "نام این فایل استخراجی چیست؟"
+
+msgid "Override %1 definition?"
+msgstr "بازنویسی \"1%\" تعاریف؟"
+
+msgid "E-mail %1"
+msgstr ""
+
+#, fuzzy
+msgid "To:"
+msgstr "به"
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "گزارش باگ"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "پیوست"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+#, fuzzy
+msgid "Remove File"
+msgstr "حذف"
+
+msgid "Wizard"
+msgstr "دستیار نصب"
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/fi.po b/sao/locale/fi.po
new file mode 100644
index 0000000000..e063a83b57
--- /dev/null
+++ b/sao/locale/fi.po
@@ -0,0 +1,650 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+#, fuzzy
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+#, fuzzy
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr ""
+
+msgid "Created at:"
+msgstr ""
+
+msgid "Edited by:"
+msgstr ""
+
+msgid "Edited at:"
+msgstr ""
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr ""
+
+msgid "Created at"
+msgstr ""
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+#, fuzzy
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+#, fuzzy
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/fr.po b/sao/locale/fr.po
new file mode 100644
index 0000000000..1eb088266a
--- /dev/null
+++ b/sao/locale/fr.po
@@ -0,0 +1,685 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Apr 06 2018 22:57:49 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Oct 09 2017 19:35:04 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr "Êtes-vous sûr de quitter ?"
+
+msgid "Favorites"
+msgstr "Favoris"
+
+msgid "Manage..."
+msgstr "Gérer..."
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "Déconnexion"
+
+msgid "Action"
+msgstr "Actions"
+
+msgid "New"
+msgstr "Nouveau"
+
+msgid "Save"
+msgstr "Sauver"
+
+msgid "Switch"
+msgstr "Basculer"
+
+msgid "Reload/Undo"
+msgstr "Recharger/Annuler"
+
+msgid "Duplicate"
+msgstr "Dupliquer"
+
+msgid "Delete"
+msgstr "Supprimer"
+
+msgid "Previous"
+msgstr "Précédent"
+
+msgid "Next"
+msgstr "Suivant"
+
+msgid "Search"
+msgstr "Rechercher"
+
+msgid "Close Tab"
+msgstr "Fermer l'onglet"
+
+msgid "Attachment"
+msgstr "Attachement"
+
+msgid "Note"
+msgstr "Note"
+
+msgid "Relate"
+msgstr "Relation"
+
+msgid "Print"
+msgstr "Imprimer"
+
+msgid "E-Mail"
+msgstr "E-mail"
+
+msgid "Previous tab"
+msgstr "Onglet précédent"
+
+msgid "Next tab"
+msgstr "Onglet suivant"
+
+msgid "Global search"
+msgstr "Recherche globale"
+
+msgid "Show this help"
+msgstr "Affiche cette aide"
+
+msgid "Keyboard shortcuts"
+msgstr "Raccourcis clavier"
+
+msgid "Close"
+msgstr "Fermer"
+
+msgid "Global shortcuts"
+msgstr "Raccourcis globaux"
+
+msgid "Tab shortcuts"
+msgstr "Raccourcis d'onglet"
+
+msgid "Unable to reach the server."
+msgstr "Impossible de joindre le serveur."
+
+msgid "Too many requests. Try again later."
+msgstr "Trop de demandes. Réessayez plus tard."
+
+msgid "Login"
+msgstr "Connexion"
+
+msgid "User name"
+msgstr "Nom d'utilisateur"
+
+msgid "Database"
+msgstr "Base de données"
+
+msgid "Your selection:"
+msgstr "Votre sélection :"
+
+msgid "Cancel"
+msgstr "Annuler"
+
+msgid "OK"
+msgstr "Valider"
+
+msgid "Y"
+msgstr "A"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "S"
+
+msgid "d"
+msgstr "j"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Par : "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "y"
+
+msgid "Yes"
+msgstr "Oui"
+
+msgid "True"
+msgstr "Vrai"
+
+msgid "t"
+msgstr "v"
+
+msgid "False"
+msgstr "Faux"
+
+msgid "Message: "
+msgstr "Message : "
+
+msgid "Warning: "
+msgstr "Avertissement : "
+
+msgid "Always ignore this warning."
+msgstr "Toujours ignorer cet avertissement."
+
+msgid "Do you want to proceed?"
+msgstr "Souhaitez-vous continuer ?"
+
+msgid "No"
+msgstr "Non"
+
+msgid "Confirmation: "
+msgstr "Confirmation : "
+
+msgid "Concurrency Exception"
+msgstr "Erreur d'accès concurrent"
+
+msgid "Write Concurrency Warning: "
+msgstr "Attention, accès en écriture concurrents : "
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+"Cet enregistrement a été modifié pendant que vous étiez en train de "
+"l'éditer."
+
+msgid "Choose:"
+msgstr "Choisissez :"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "« Annuler » pour annuler vos modifications ;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "« Comparer » pour voir la nouvelle version ;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "« Écraser » pour sauver vos modifications."
+
+msgid "Compare"
+msgstr "Comparer"
+
+msgid "Compare: %1"
+msgstr "Comparer : %1"
+
+msgid "Write Anyway"
+msgstr "Écraser"
+
+msgid "Application Error"
+msgstr "Erreur applicative"
+
+msgid "Report Bug"
+msgstr "Rapporter un bogue"
+
+msgid "Processing"
+msgstr "Traitement en cours"
+
+msgid "Search..."
+msgstr "Recherche..."
+
+msgid "Create..."
+msgstr "Créer..."
+
+msgid "Download"
+msgstr "Télécharger"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Police"
+
+msgid "Size"
+msgstr "Taille"
+
+msgid "Switch view"
+msgstr "Basculer la vue"
+
+msgid "Previous Record"
+msgstr "Enregistrement précédent"
+
+msgid "Next Record"
+msgstr "Enregistrement suivant"
+
+msgid "Create a new record"
+msgstr "Créer un nouvel enregistrement"
+
+msgid "Save this record"
+msgstr "Sauver cet enregistrement"
+
+msgid "Reload"
+msgstr "Recharger"
+
+msgid "View Logs..."
+msgstr "Voir les Logs..."
+
+msgid "Show revisions..."
+msgstr "Afficher les révisions..."
+
+msgid "Add an attachment to the record"
+msgstr "Ajouter un attachement à l'enregistrement"
+
+msgid "Add a note to the record"
+msgstr "Ajouter une note à l'enregistrement"
+
+msgid "E-Mail..."
+msgstr "E-mail..."
+
+msgid "Send an e-mail using the record"
+msgstr "Envoyer un e-mail en utilisant l'enregistrement"
+
+msgid "Export"
+msgstr "Exporter"
+
+msgid "Import"
+msgstr "Importer"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"L'action suivante nécessite de fermer tous les onglets.\n"
+"Voulez-vous continuer ?"
+
+msgid "Launch action"
+msgstr "Lancer une action"
+
+msgid "Open related records"
+msgstr "Ouvrir les enregistrements liés"
+
+msgid "Print report"
+msgstr "Imprimer un rapport"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Cet enregistrement a été modifié\n"
+"voulez-vous le sauvegarder ?"
+
+msgid "Record saved."
+msgstr "Enregistrement sauvegardé."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Sur les enregistrement(s) dupliqué(s) maintenant."
+
+msgid "Are you sure to remove this record?"
+msgstr "Êtes-vous sûr de vouloir supprimer cet enregistrement ?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Êtes-vous sûr de vouloir supprimer ces enregistrements ?"
+
+msgid "Records removed."
+msgstr "Enregistrements supprimés."
+
+msgid "Records not removed."
+msgstr "Enregistrements non supprimés."
+
+msgid "You have to select one record."
+msgstr "Vous devez sélectionner un enregistrement."
+
+msgid "ID:"
+msgstr "ID :"
+
+msgid "Created by:"
+msgstr "Crée par :"
+
+msgid "Created at:"
+msgstr "Crée le :"
+
+msgid "Edited by:"
+msgstr "Édité par :"
+
+msgid "Edited at:"
+msgstr "Édité le :"
+
+msgid "Model: "
+msgstr "Modèle : "
+
+msgid "Add..."
+msgstr "Ajouter..."
+
+msgid "Preview"
+msgstr "Aperçu"
+
+msgid "Template"
+msgstr "Modèle"
+
+msgid "Attachment (%1)"
+msgstr "Attachement (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Note (%1/%2)"
+
+msgid " of "
+msgstr " de "
+
+msgid "Filters"
+msgstr "Filtres"
+
+msgid "Clear Search"
+msgstr "Effacer la recherche"
+
+msgid "Bookmarks"
+msgstr "Marques-pages"
+
+msgid "Remove this bookmark"
+msgstr "Supprimer ce marque-page"
+
+msgid "Bookmark this filter"
+msgstr "Sauver ce filtre"
+
+msgid "Bookmark Name:"
+msgstr "Nom du marque-page :"
+
+msgid "Show active records"
+msgstr "Montrer les enregistrements actifs"
+
+msgid "Show inactive records"
+msgstr "Montrer les enregistrements inactifs"
+
+msgid "Find"
+msgstr "Chercher"
+
+msgid "From"
+msgstr "De"
+
+msgid "To"
+msgstr "À"
+
+msgid "Open the calendar"
+msgstr "Ouvrir le calendrier"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Créé par"
+
+msgid "Created at"
+msgstr "Crée le"
+
+msgid "Modified by"
+msgstr "Modifié par"
+
+msgid "Modified at"
+msgstr "Modifié le"
+
+msgid "\"%1\" is required."
+msgstr "« %1 » est requis."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "« %1 » n'est pas valide selon son domaine."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "Les valeurs de « %1 » ne sont pas valides."
+
+msgid "Pre-validation"
+msgstr "Pré-validation"
+
+msgid ":"
+msgstr " :"
+
+msgid "Translate"
+msgstr "Traduire"
+
+msgid "Edit"
+msgstr "Éditer"
+
+msgid "Fuzzy"
+msgstr "Floue"
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+"Vous devez sauvegarder l'enregistrement avant d'ajouter des traductions."
+
+msgid "No other language available."
+msgstr "Pas d'autre langue disponible."
+
+msgid "Show"
+msgstr "Afficher"
+
+msgid "Hide"
+msgstr "Masquer"
+
+msgid "Open the record"
+msgstr "Ouvrir l'enregistrement"
+
+msgid "Clear the field"
+msgstr "Effacer le champ"
+
+msgid "Search a record"
+msgstr "Chercher un enregistrement"
+
+msgid "Add"
+msgstr "Ajouter"
+
+msgid "Remove"
+msgstr "Enlever"
+
+msgid "Open"
+msgstr "Ouvrir"
+
+msgid "Undelete"
+msgstr "Restaurer"
+
+msgid "Save As"
+msgstr "Enregistrer sous"
+
+msgid "Save As..."
+msgstr "Enregistrer sous..."
+
+msgid "Select"
+msgstr "Sélectionner"
+
+msgid "Select..."
+msgstr "Sélectionner..."
+
+msgid "Clear"
+msgstr "Effacer"
+
+msgid "Choose a language"
+msgstr "Choisir une langue"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr " : "
+
+msgid "More"
+msgstr "Plus"
+
+msgid "Today"
+msgstr "Aujourd'hui"
+
+msgid "Month"
+msgstr "Mois"
+
+msgid "Week"
+msgstr "Semaine"
+
+msgid "Day"
+msgstr "Jour"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Sélectionnez votre action"
+
+msgid "No action defined."
+msgstr "Pas d'action définie."
+
+msgid "Attachments (%1)"
+msgstr "Attachements (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notes (%1)"
+
+msgid "Search %1"
+msgstr "Recherche %1"
+
+msgid "Revision"
+msgstr "Révision"
+
+msgid "All Fields"
+msgstr "Tous les champs"
+
+msgid "Fields Selected"
+msgstr "Champs sélectionnés"
+
+msgid "CSV Parameters"
+msgstr "Paramètres CSV"
+
+msgid "Delimiter:"
+msgstr "Délimiteur :"
+
+msgid "Quote Char:"
+msgstr "Caractère de guillemet :"
+
+msgid "CSV Import: %1"
+msgstr "Import CSV : %1"
+
+msgid "Auto-Detect"
+msgstr "Détection automatique"
+
+msgid "File to Import"
+msgstr "Fichier à importer"
+
+msgid "Encoding:"
+msgstr "Codage :"
+
+msgid "Lines to Skip:"
+msgstr "Lignes à ignorer :"
+
+msgid "You must select an import file first."
+msgstr "Vous devez d'abord sélectionner un fichier d'import."
+
+msgid "An error occured in loading the file."
+msgstr "Une erreur a eu lieu au chargement du fichier."
+
+msgid "Error processing the file at field %1."
+msgstr "Erreur lors du traitement du fichier du champ %1."
+
+msgid "Error"
+msgstr "Erreur"
+
+msgid "Error occured in loading the file"
+msgstr "Une erreur a eu lieu au chargement du fichier"
+
+msgid "CSV Export: %1"
+msgstr "Export CSV : %1"
+
+msgid "Save Export"
+msgstr "Sauver l'exportation"
+
+msgid "URL Export"
+msgstr "URL d'exportation"
+
+msgid "Delete Export"
+msgstr "Supprimer l'exportation"
+
+msgid "Predefined Exports"
+msgstr "Exportations prédéfinies"
+
+msgid "Selected Records"
+msgstr "Enregistrements sélectionnés"
+
+msgid "Listed Records"
+msgstr "Enregistrements listés"
+
+msgid "Export:"
+msgstr "Exporter :"
+
+msgid "Ignore search limit"
+msgstr "Ignorer la limite de recherche"
+
+msgid "Use locale format"
+msgstr "Utiliser le format local"
+
+msgid "Add Field Names"
+msgstr "Ajouter les noms des champs"
+
+msgid "%1 (string)"
+msgstr "%1 (string)"
+
+msgid "%1 (model name)"
+msgstr "%1 (nom de l'enregistrement)"
+
+msgid "%1 (record name)"
+msgstr "%1 (nom de l'enregistrement)"
+
+msgid "What is the name of this export?"
+msgstr "Quel est le nom de cet export ?"
+
+msgid "Override %1 definition?"
+msgstr "Surcharger la définition %1 ?"
+
+msgid "E-mail %1"
+msgstr "E-mail %1"
+
+msgid "To:"
+msgstr "À :"
+
+msgid "Cc:"
+msgstr "Cc :"
+
+msgid "Bcc:"
+msgstr "Cci :"
+
+msgid "Subject:"
+msgstr "Objet :"
+
+msgid "Reports"
+msgstr "Rapports"
+
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-19 21:49+0000\n"
+"PO-Revision-Date: 2021-04-19 21:49+0000\n"
+"Last-Translator: Cedric Krier \n"
+"Language-Team: LANGUAGE \n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 4.5.3\n"
+
+msgid "Attachments"
+msgstr "Pièces jointes"
+
+msgid "Files"
+msgstr "Fichiers"
+
+msgid "Send"
+msgstr "Envoyer"
+
+msgid "Remove File"
+msgstr "Supprimer le fichier"
+
+msgid "Wizard"
+msgstr "Assistant"
+
+msgid "Translate view"
+msgstr "Traduire la vue"
diff --git a/sao/locale/hu.po b/sao/locale/hu.po
new file mode 100644
index 0000000000..f4549239e7
--- /dev/null
+++ b/sao/locale/hu.po
@@ -0,0 +1,670 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr "Könyvjelzők"
+
+msgid "Manage..."
+msgstr "Módosítás..."
+
+msgid "Logout"
+msgstr "Kilépés"
+
+msgid "Action"
+msgstr "Művelet"
+
+msgid "New"
+msgstr "Új"
+
+msgid "Save"
+msgstr "Mentés"
+
+msgid "Switch"
+msgstr "Átvált"
+
+msgid "Reload/Undo"
+msgstr "Újratöltés / Visszavonás"
+
+msgid "Duplicate"
+msgstr "Duplázás"
+
+msgid "Delete"
+msgstr "Törlés"
+
+msgid "Previous"
+msgstr "Vissza"
+
+msgid "Next"
+msgstr "Tovább"
+
+msgid "Search"
+msgstr "Keresés"
+
+msgid "Close Tab"
+msgstr "Lap bezárása"
+
+msgid "Attachment"
+msgstr "Melléklet"
+
+msgid "Note"
+msgstr "Jegyzetek"
+
+msgid "Relate"
+msgstr "Kapcsolat"
+
+msgid "Print"
+msgstr "Nyomtatás"
+
+msgid "E-Mail"
+msgstr "E-mail"
+
+msgid "Previous tab"
+msgstr "Előző lap"
+
+msgid "Next tab"
+msgstr "Következő lap"
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr "Gyorsbillentyűk"
+
+msgid "Close"
+msgstr "Bezárás"
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "Bejelentkezés"
+
+msgid "User name"
+msgstr "Felhasználónév"
+
+msgid "Database"
+msgstr "Adatbázis"
+
+msgid "Your selection:"
+msgstr "Az ön választásai:"
+
+msgid "Cancel"
+msgstr "Mégse"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "É"
+
+msgid "M"
+msgstr "H"
+
+msgid "w"
+msgstr "h"
+
+msgid "d"
+msgstr "t"
+
+msgid "h"
+msgstr "ó"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr ""
+
+#, fuzzy
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "J"
+
+msgid "Yes"
+msgstr "Igen"
+
+msgid "True"
+msgstr "Igaz"
+
+msgid "t"
+msgstr "w"
+
+msgid "False"
+msgstr "Hamis"
+
+msgid "Message: "
+msgstr "Üzenet: "
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr "Ne mutassa többet ezt a figyelmeztetést"
+
+msgid "Do you want to proceed?"
+msgstr "Akarja folyatni?"
+
+msgid "No"
+msgstr "Nem"
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr "Egyidejű módosítási hiba"
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+"Ezt a rekordot valaki más módosította az idő alatt, míg Ön dolgozott rajta."
+
+msgid "Choose:"
+msgstr "Válasszon:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "„Mégse”, hogy ne mentse el;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+"„Összehasonlítás”, hogy megtekintse a másik felhasználó által módosított "
+"változatot;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "„Felülírás”, hogy a másik felhasználóét az Ön verziójával felülírja."
+
+msgid "Compare"
+msgstr "Összehasonlítás"
+
+msgid "Compare: %1"
+msgstr "Összehasonlítás: %1"
+
+msgid "Write Anyway"
+msgstr "Felülírás"
+
+msgid "Application Error"
+msgstr "Alkalmazáshiba"
+
+msgid "Report Bug"
+msgstr "A hiba jelentése"
+
+msgid "Processing"
+msgstr "Folyamatban"
+
+msgid "Search..."
+msgstr "Keresés..."
+
+msgid "Create..."
+msgstr "Létrehozás..."
+
+msgid "Download"
+msgstr "Letöltés"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Betűtípus"
+
+msgid "Size"
+msgstr "Méret"
+
+msgid "Switch view"
+msgstr "Nézet váltása"
+
+msgid "Previous Record"
+msgstr "Előző rekord"
+
+msgid "Next Record"
+msgstr "Következő rekord"
+
+msgid "Create a new record"
+msgstr "Új rekord létrehozása"
+
+msgid "Save this record"
+msgstr "Rekord mentése"
+
+msgid "Reload"
+msgstr "Újratöltés"
+
+msgid "View Logs..."
+msgstr "Napló megtekintése..."
+
+msgid "Show revisions..."
+msgstr "Feldolgozások megtekintése"
+
+msgid "Add an attachment to the record"
+msgstr "Melléklet hozzáadása a rekordhoz"
+
+msgid "Add a note to the record"
+msgstr "Jegyzet hozzáadása a rekordhoz"
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr "E-mail küldése a rekord adataival"
+
+msgid "Export"
+msgstr "Exportálás"
+
+msgid "Import"
+msgstr "Importálás"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"A továbblépéshez be kell zárnia minden nyitott lapot.\n"
+"Biztosan tovább akar lépni?"
+
+msgid "Launch action"
+msgstr "Művelet indítása"
+
+msgid "Open related records"
+msgstr "Kapcsolódó rekordok megnyitása"
+
+msgid "Print report"
+msgstr "Nyomtatás"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Módosította ezt a rekordot,\n"
+"el akarja menteni?"
+
+msgid "Record saved."
+msgstr "Rekord elmentve."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Most az új másolaton(okon) dolgozik."
+
+msgid "Are you sure to remove this record?"
+msgstr "Biztos törölni szeretné?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Biztos törölni szeretné?"
+
+msgid "Records removed."
+msgstr "Rekordok törölve."
+
+msgid "Records not removed."
+msgstr "A rekordok nem lettek törölve."
+
+msgid "You have to select one record."
+msgstr "Ki kell választania egy rekordot."
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr "Létrehozta:"
+
+msgid "Created at:"
+msgstr "Létrehozás dátuma:"
+
+msgid "Edited by:"
+msgstr "Módosította:"
+
+msgid "Edited at:"
+msgstr "Módosítás dátuma:"
+
+msgid "Model: "
+msgstr "Elem: "
+
+msgid "Add..."
+msgstr "Hozzáadás..."
+
+msgid "Preview"
+msgstr "Előnézet"
+
+msgid "Template"
+msgstr "Sablon"
+
+msgid "Attachment (%1)"
+msgstr "Melléklet (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Jegyzet (%1/%2)"
+
+msgid " of "
+msgstr " , összesen "
+
+msgid "Filters"
+msgstr "Szűrők"
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr "Elmentett szűrők"
+
+msgid "Remove this bookmark"
+msgstr "Ez az elmentett szűrő eltávolítása"
+
+msgid "Bookmark this filter"
+msgstr "Ez a szűrő elmentése"
+
+msgid "Bookmark Name:"
+msgstr "Könyvjelző neve:"
+
+msgid "Show active records"
+msgstr "Aktív rekordok megjelenítése"
+
+msgid "Show inactive records"
+msgstr "Inaktív rekordok megjelenítése"
+
+msgid "Find"
+msgstr "Keresés"
+
+msgid "From"
+msgstr "Küldő"
+
+msgid "To"
+msgstr "Címzett"
+
+msgid "Open the calendar"
+msgstr "Naptár megnyitása"
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr "Létrehozta"
+
+msgid "Created at"
+msgstr "Létrehozás dátuma"
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr "Adatkitöltés ellenőrzése"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Fordítás"
+
+msgid "Edit"
+msgstr "Módosítás"
+
+msgid "Fuzzy"
+msgstr "Pontatlan"
+
+msgid "You need to save the record before adding translations."
+msgstr "El kell menteni a rekordot mielőtt hozzáadja a fordítást."
+
+msgid "No other language available."
+msgstr "Másik nyelv nem elérhető."
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr "Elrejtés"
+
+msgid "Open the record"
+msgstr "Rekord megnyitása"
+
+msgid "Clear the field"
+msgstr "A mező kiürítése"
+
+msgid "Search a record"
+msgstr "Rekord keresése"
+
+msgid "Add"
+msgstr "Hozzáad"
+
+msgid "Remove"
+msgstr "Eltávolítás"
+
+msgid "Open"
+msgstr "Megnyitás"
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr "Mentés másként"
+
+msgid "Save As..."
+msgstr "Mentés másként..."
+
+msgid "Select"
+msgstr "Kiválaszt"
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr "Törlés"
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr "Ma"
+
+msgid "Month"
+msgstr "Hónap"
+
+msgid "Week"
+msgstr "Hét"
+
+msgid "Day"
+msgstr "Nap"
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr "Válasszon műveletet"
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr "Mellékletek (%1)"
+
+msgid "Notes (%1)"
+msgstr "Jegyzetek (%1)"
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr "Szerkesztés"
+
+msgid "All Fields"
+msgstr "Összes mező"
+
+msgid "Fields Selected"
+msgstr "Kiválasztott mezők"
+
+msgid "CSV Parameters"
+msgstr "CSV paraméterek"
+
+msgid "Delimiter:"
+msgstr "Elválasztójel:"
+
+msgid "Quote Char:"
+msgstr "Karakterlánc-elválasztó:"
+
+msgid "CSV Import: %1"
+msgstr "Importálás CSV-ből: %1"
+
+msgid "Auto-Detect"
+msgstr "Automatikus felismerés"
+
+msgid "File to Import"
+msgstr "Importadatok"
+
+msgid "Encoding:"
+msgstr "Kódolás:"
+
+msgid "Lines to Skip:"
+msgstr "Átugrandó sorok:"
+
+msgid "You must select an import file first."
+msgstr "Először adja meg a betöltendő fájlt."
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr "Hiba"
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr "Exportálás CSV-be: %1"
+
+msgid "Save Export"
+msgstr "Oszloplista elmentése"
+
+msgid "URL Export"
+msgstr "Exportálás URL-je"
+
+msgid "Delete Export"
+msgstr "Oszloplista törlése"
+
+msgid "Predefined Exports"
+msgstr "Elmentett oszloplisták"
+
+msgid "Selected Records"
+msgstr "Csak a kiválasztott rekordokat"
+
+msgid "Listed Records"
+msgstr "A listán szereplő összes rekordot"
+
+msgid "Export:"
+msgstr "Exportálás:"
+
+msgid "Ignore search limit"
+msgstr "Keresési korlát nélkül"
+
+msgid "Use locale format"
+msgstr "A felhasználó nyelvén"
+
+msgid "Add Field Names"
+msgstr "Mezőnevek hozzáadása"
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr "Mi legyen az oszloplista neve?"
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr "Címzett:"
+
+msgid "Cc:"
+msgstr "Másolatot kap:"
+
+msgid "Bcc:"
+msgstr "Rejtett másolat:"
+
+msgid "Subject:"
+msgstr "Tárgy:"
+
+msgid "Reports"
+msgstr "Nyomtatványok"
+
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-10 10:06+0000\n"
+"PO-Revision-Date: 2021-04-10 10:06+0000\n"
+"Last-Translator: Dukai Gábor \n"
+"Language-Team: LANGUAGE \n"
+"Language: hu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.5.2\n"
+
+msgid "Attachments"
+msgstr "Mellékletek"
+
+msgid "Files"
+msgstr "Fájlok"
+
+msgid "Send"
+msgstr "Küldés"
+
+msgid "Remove File"
+msgstr "Fájl eltávolítása"
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr "Nézet fordítása."
diff --git a/sao/locale/id.po b/sao/locale/id.po
new file mode 100644
index 0000000000..7f117538a5
--- /dev/null
+++ b/sao/locale/id.po
@@ -0,0 +1,652 @@
+# Generated by grunt-xgettext on Sat Jun 01 2019 23:48:54 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr "Tindakan"
+
+msgid "New"
+msgstr "Baru"
+
+msgid "Save"
+msgstr "Simpan"
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr "Hapus"
+
+msgid "Previous"
+msgstr "Sebelumnya"
+
+msgid "Next"
+msgstr "Berikutnya"
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr "Lampiran"
+
+msgid "Note"
+msgstr "Catatan"
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr "Cetak"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr "Tutup"
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr "Terlalu banyak permintaan. Coba lagi nanti."
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr "Pilihan anda:"
+
+msgid "Cancel"
+msgstr "Batal"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "Y"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "w"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Oleh: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "y"
+
+msgid "Yes"
+msgstr "Ya"
+
+msgid "True"
+msgstr "Benar"
+
+msgid "t"
+msgstr "t"
+
+msgid "False"
+msgstr "Salah"
+
+msgid "Message: "
+msgstr "Pesan: "
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr "Selalu abaikan peringatan ini."
+
+msgid "Do you want to proceed?"
+msgstr "Apakah Anda ingin melanjutkan?"
+
+msgid "No"
+msgstr "Tidak"
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr "Rekaman ini telah diubah selagi anda menyuntingnya."
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr "Bandingkan"
+
+msgid "Compare: %1"
+msgstr "Bandingkan: %1"
+
+msgid "Write Anyway"
+msgstr "Tetap Menulis"
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr "Melaporkan Kesalahan"
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr "Unduh"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr "E-Mail..."
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Tindakan berikut ini perlu untuk menutup semua tab.\n"
+"Apakah Anda ingin melanjutkan?"
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr "Cetak laporan"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Dibuat oleh:"
+
+msgid "Created at:"
+msgstr "Dibuat pada:"
+
+msgid "Edited by:"
+msgstr "Disunting oleh:"
+
+msgid "Edited at:"
+msgstr "Disunting pada:"
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+#, fuzzy
+msgid "Preview"
+msgstr "Sebelumnya"
+
+msgid "Template"
+msgstr "Templat"
+
+msgid "Attachment (%1)"
+msgstr "Lampiran (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Catatan (%1/%2)"
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr "Kepada"
+
+msgid "Open the calendar"
+msgstr "Buka kalender"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Dibuat oleh"
+
+msgid "Created at"
+msgstr "Dibuat pada"
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr "Sunting"
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr "Tidak ada bahasa lain yang tersedia."
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr "Tambahkan"
+
+msgid "Remove"
+msgstr "Hapus"
+
+msgid "Open"
+msgstr "Buka"
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr "Simpan Sebagai"
+
+msgid "Save As..."
+msgstr "Simpan Sebagai..."
+
+msgid "Select"
+msgstr "Pilih"
+
+msgid "Select..."
+msgstr "Pilih..."
+
+msgid "Clear"
+msgstr "Bersihkan"
+
+msgid "Choose a language"
+msgstr "Pilih bahasa"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr "Hari ini"
+
+msgid "Month"
+msgstr "Bulan"
+
+msgid "Week"
+msgstr "Pekan"
+
+msgid "Day"
+msgstr "Hari"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Pilih tindakan Anda"
+
+msgid "No action defined."
+msgstr "Tidak ada tindakan yang ditentukan."
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr "Revisi"
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr "Kepada:"
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr "Subject:"
+
+#, fuzzy
+msgid "Reports"
+msgstr "Melaporkan Kesalahan"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "Lampiran"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr "Kirim"
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Hapus"
+
+msgid "Wizard"
+msgstr "Wisaya"
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/it.po b/sao/locale/it.po
new file mode 100644
index 0000000000..a0aabf92a9
--- /dev/null
+++ b/sao/locale/it.po
@@ -0,0 +1,684 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr "Sei sicuro di voler abbandonare?"
+
+msgid "Favorites"
+msgstr "Preferiti"
+
+msgid "Manage..."
+msgstr "Gestisci..."
+
+msgid "Logout"
+msgstr "Esci"
+
+msgid "Action"
+msgstr "Azione"
+
+msgid "New"
+msgstr "Nuovo"
+
+msgid "Save"
+msgstr "Salva"
+
+msgid "Switch"
+msgstr "Scambia"
+
+msgid "Reload/Undo"
+msgstr "Ricarica/Ripristina"
+
+msgid "Duplicate"
+msgstr "Duplica"
+
+msgid "Delete"
+msgstr "Elimina"
+
+msgid "Previous"
+msgstr "Precedente"
+
+msgid "Next"
+msgstr "Successivo"
+
+msgid "Search"
+msgstr "Cerca"
+
+msgid "Close Tab"
+msgstr "Chiudi la scheda"
+
+msgid "Attachment"
+msgstr "Allegato"
+
+msgid "Note"
+msgstr "nota"
+
+msgid "Relate"
+msgstr "collega"
+
+msgid "Print"
+msgstr "Stampa"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr "Scheda precedente"
+
+msgid "Next tab"
+msgstr "Scheda successiva"
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr "Chiudi"
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr "server non raggiungibile"
+
+msgid "Too many requests. Try again later."
+msgstr "Troppe richieste. Riprovare più tardi."
+
+msgid "Login"
+msgstr "Login"
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr "Database"
+
+msgid "Your selection:"
+msgstr "la tua selezione"
+
+msgid "Cancel"
+msgstr "Annulla"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "Y"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "w"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "per: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "y"
+
+msgid "Yes"
+msgstr "Sì"
+
+msgid "True"
+msgstr "Vero"
+
+msgid "t"
+msgstr "t"
+
+msgid "False"
+msgstr "Falso"
+
+msgid "Message: "
+msgstr "messaggio: "
+
+msgid "Warning: "
+msgstr "Attenzione: "
+
+msgid "Always ignore this warning."
+msgstr "ignorare sempre questo avviso"
+
+msgid "Do you want to proceed?"
+msgstr "Continuare?"
+
+msgid "No"
+msgstr "no"
+
+msgid "Confirmation: "
+msgstr "conferma: "
+
+msgid "Concurrency Exception"
+msgstr "eccezione di concorrenza"
+
+msgid "Write Concurrency Warning: "
+msgstr "scrivi avviso di concorrenza: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Questo record è stato modificato mentre lo stavi compilando."
+
+msgid "Choose:"
+msgstr "Scegli:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Annulla\" per annullare il salvataggio;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Confronta\" per vedere la versione modificata;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Scrivi comunque\" per salvare la versione corrente"
+
+msgid "Compare"
+msgstr "Confronta"
+
+msgid "Compare: %1"
+msgstr "Confronta: %1"
+
+msgid "Write Anyway"
+msgstr "Scrivi comunque"
+
+msgid "Application Error"
+msgstr "Errore dell'applicazione"
+
+msgid "Report Bug"
+msgstr "Segnala l'errore"
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr "Cerca..."
+
+msgid "Create..."
+msgstr "Crea..."
+
+msgid "Download"
+msgstr "Scarica"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Carattere"
+
+msgid "Size"
+msgstr "Dimensione"
+
+msgid "Switch view"
+msgstr "cambia visualizzazione"
+
+msgid "Previous Record"
+msgstr "Record precedente"
+
+msgid "Next Record"
+msgstr "Record successivo"
+
+msgid "Create a new record"
+msgstr "Crea un nuovo record"
+
+msgid "Save this record"
+msgstr "salvare il record"
+
+msgid "Reload"
+msgstr "Ricarica"
+
+msgid "View Logs..."
+msgstr "vedi logs"
+
+msgid "Show revisions..."
+msgstr "mostra revisioni"
+
+msgid "Add an attachment to the record"
+msgstr "aggiungere un allegato al record"
+
+msgid "Add a note to the record"
+msgstr "Aggiungi un appunto sul record"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "Aggiungi un appunto sul record"
+
+msgid "Export"
+msgstr "Esporta"
+
+msgid "Import"
+msgstr "Importa"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"la seguente azione richiede la chiusura di tutte le finestre. Intendi "
+"continuare?"
+
+msgid "Launch action"
+msgstr "lancia applicazione"
+
+msgid "Open related records"
+msgstr "apri record collegati"
+
+msgid "Print report"
+msgstr "Stampa rapporto"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Questo record è stato modificato\n"
+"intendi salvarlo?"
+
+msgid "Record saved."
+msgstr "Record salvato."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Stai ora lavorando nel record duplicato."
+
+msgid "Are you sure to remove this record?"
+msgstr "sei sicuro di rimuovere questo record?"
+
+msgid "Are you sure to remove those records?"
+msgstr "sei sicuro di rimuovere questi record?"
+
+msgid "Records removed."
+msgstr "Record rimossi."
+
+msgid "Records not removed."
+msgstr "Record non rimossi."
+
+msgid "You have to select one record."
+msgstr "Devi selezionare un record."
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Creato da:"
+
+msgid "Created at:"
+msgstr "Creato il:"
+
+msgid "Edited by:"
+msgstr "Modificato da:"
+
+msgid "Edited at:"
+msgstr "Modifica il:"
+
+msgid "Model: "
+msgstr "Modello: "
+
+msgid "Add..."
+msgstr "Inserisci..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "Precedente"
+
+#, fuzzy
+msgid "Template"
+msgstr "collega"
+
+#, fuzzy
+msgid "Attachment (%1)"
+msgstr "allegati (%1)"
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr "Filtri"
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr "Segnalibri"
+
+msgid "Remove this bookmark"
+msgstr "rimuovere questo segnalibro"
+
+msgid "Bookmark this filter"
+msgstr "contrassegnare questo filtro"
+
+msgid "Bookmark Name:"
+msgstr "Nome segnalibro"
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr "Trova"
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgid "Created by"
+msgstr "Crea..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "creato il "
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "Data di modifica "
+
+#, fuzzy
+msgid "\"%1\" is required."
+msgstr "\"%1\" è obbligatorio"
+
+#, fuzzy
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" non è valido rispetto al dominio"
+
+#, fuzzy
+msgid "The values of \"%1\" are not valid."
+msgstr "i valori di \"%1\" non sono validi"
+
+msgid "Pre-validation"
+msgstr "pre-validazione"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Traduci"
+
+msgid "Edit"
+msgstr "Modifica"
+
+msgid "Fuzzy"
+msgstr "fuzzy"
+
+msgid "You need to save the record before adding translations."
+msgstr "devi salvare il record prima di inserire la traduzione"
+
+msgid "No other language available."
+msgstr "non sono disponibili altre lingue"
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr "Aggiungi"
+
+msgid "Remove"
+msgstr "Rimuovi"
+
+msgid "Open"
+msgstr "Apri"
+
+msgid "Undelete"
+msgstr "Annullare l'eliminazione"
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr "Seleziona"
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr "Cancella"
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr "%1%"
+
+#, fuzzy
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "più"
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Seleziona l'azione"
+
+msgid "No action defined."
+msgstr "Nessuna azione definita."
+
+msgid "Attachments (%1)"
+msgstr "allegati (%1)"
+
+msgid "Notes (%1)"
+msgstr "note (%1)"
+
+msgid "Search %1"
+msgstr "Cerca %1"
+
+msgid "Revision"
+msgstr "Revisione"
+
+msgid "All Fields"
+msgstr "Ogni Campo"
+
+msgid "Fields Selected"
+msgstr "campi selezionati"
+
+msgid "CSV Parameters"
+msgstr "parametri CSV"
+
+msgid "Delimiter:"
+msgstr "Delimitatore:"
+
+msgid "Quote Char:"
+msgstr "Carattere tra virgolette:"
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr "Autorilevamento"
+
+msgid "File to Import"
+msgstr "file da importare"
+
+msgid "Encoding:"
+msgstr "Codifica:"
+
+msgid "Lines to Skip:"
+msgstr "righe da saltare"
+
+#, fuzzy
+msgid "You must select an import file first."
+msgstr "va prima selezionato un file da importare"
+
+#, fuzzy
+msgid "An error occured in loading the file."
+msgstr "si è verificato un errore nel caricamento del file"
+
+msgid "Error processing the file at field %1."
+msgstr "errore nell'elaborazione del file nel campo %1"
+
+msgid "Error"
+msgstr "Errore"
+
+msgid "Error occured in loading the file"
+msgstr "si è verificato un errore nel caricamento del file"
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr "salvare esportazione"
+
+#, fuzzy
+msgid "URL Export"
+msgstr "Esporta"
+
+msgid "Delete Export"
+msgstr "eliminare esportazione"
+
+msgid "Predefined Exports"
+msgstr "esportazioni predefinite"
+
+msgid "Selected Records"
+msgstr "Record selezionati"
+
+msgid "Listed Records"
+msgstr "Record elencati"
+
+#, fuzzy
+msgid "Export:"
+msgstr "Esporta"
+
+msgid "Ignore search limit"
+msgstr "Ignora limite di ricerca"
+
+msgid "Use locale format"
+msgstr "Usa il formato locale"
+
+msgid "Add Field Names"
+msgstr "inserire nomi campo"
+
+msgid "%1 (string)"
+msgstr "%1 (stringa)"
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr "qual è il nome di questa esportazione?"
+
+msgid "Override %1 definition?"
+msgstr "sovrascrivere definizione %1?"
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "Segnala l'errore"
+
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-10-16 15:42+0000\n"
+"PO-Revision-Date: 2020-10-16 15:42+0000\n"
+"Last-Translator: Luca Cristaldi \n"
+"Language-Team: LANGUAGE \n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.2.2\n"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "Allegato"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Rimuovi"
+
+msgid "Wizard"
+msgstr "wizard"
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/lo.po b/sao/locale/lo.po
new file mode 100644
index 0000000000..7370aca813
--- /dev/null
+++ b/sao/locale/lo.po
@@ -0,0 +1,689 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr "ທີ່ມັກ"
+
+msgid "Manage..."
+msgstr ""
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "ອອກລະບົບ"
+
+msgid "Action"
+msgstr "ດຳເນີນການ"
+
+msgid "New"
+msgstr "ໃໝ່"
+
+msgid "Save"
+msgstr "ບັນທຶກ"
+
+msgid "Switch"
+msgstr "ສະຫຼັບ"
+
+msgid "Reload/Undo"
+msgstr "ໂຫຼດຄືນໃໝ່/ຍົກເລີກ"
+
+msgid "Duplicate"
+msgstr "ຊໍ້າກັນ"
+
+msgid "Delete"
+msgstr "ລຶບ"
+
+msgid "Previous"
+msgstr "ກ່ອນໜ້າ"
+
+msgid "Next"
+msgstr "ຕໍ່ໄປ"
+
+msgid "Search"
+msgstr "ຊອກຫາ"
+
+msgid "Close Tab"
+msgstr "ອັດແຖບງານ"
+
+msgid "Attachment"
+msgstr "ຄັດຕິດ"
+
+msgid "Note"
+msgstr "ບັນທຶກຂໍ້ຄວາມ"
+
+msgid "Relate"
+msgstr "ກ່ຽວພັນກັບ"
+
+msgid "Print"
+msgstr "ພິມອອກ"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr "ອັດ"
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr "ບໍ່ສາມາດເຂົ້າເຖິງແມ່ຂ່າຍໄດ້"
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "ເຂົ້າສູ່ລະບົບ"
+
+msgid "User name"
+msgstr "ຊື່ຜູ້ໃຊ້"
+
+msgid "Database"
+msgstr "ຖານຂໍ້ມູນ"
+
+msgid "Your selection:"
+msgstr "ການເລືອກຂອງທ່ານ:"
+
+msgid "Cancel"
+msgstr "ຍົກເລີກ"
+
+msgid "OK"
+msgstr "ຕົກລົງ"
+
+msgid "Y"
+msgstr "ປ"
+
+msgid "M"
+msgstr "ດ"
+
+msgid "w"
+msgstr "ອ"
+
+msgid "d"
+msgstr "ວ"
+
+msgid "h"
+msgstr "ມ"
+
+msgid "m"
+msgstr "ນ"
+
+msgid "s"
+msgstr "ວ"
+
+msgid "By: "
+msgstr "ໂດຍ: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "ມ"
+
+msgid "Yes"
+msgstr "ແມ່ນ"
+
+msgid "True"
+msgstr "ຈິງ"
+
+msgid "t"
+msgstr "ຈ"
+
+msgid "False"
+msgstr "ເທັດ"
+
+msgid "Message: "
+msgstr "ຂໍ້ຄວາມ: "
+
+msgid "Warning: "
+msgstr "ຄຳເຕືອນ: "
+
+msgid "Always ignore this warning."
+msgstr "ບໍ່ສົນກັບຄຳເຕືອນນີ້."
+
+msgid "Do you want to proceed?"
+msgstr "ທ່ານຕ້ອງການດຳເນີນບໍ່?"
+
+msgid "No"
+msgstr "ບໍ່"
+
+msgid "Confirmation: "
+msgstr "ຄຳຢັ້ງຢືນ: "
+
+msgid "Concurrency Exception"
+msgstr "ຍົກເວັ້ນສະກຸນເງິນ"
+
+msgid "Write Concurrency Warning: "
+msgstr "ຂຽນຄຳເຕືອນສະກຸນເງິນ: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "ແຖວຂໍ້ມູນນີ້ຖືກດັດແປງແລ້ວໃນຂະນະທີ່ທ່ານແກ້ໄຂມັນ."
+
+msgid "Choose:"
+msgstr "ເລືອກເອົາ:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"ຍົກເລີກ\" ເພື່ອຍົກເລີກການບັນທຶກ;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"ສົມທຽບ\" ເພື່ອເບິ່ງທຽບກັບສະບັບດັດແປງແລ້ວ;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"ຂຽນລົງເລີຍ\" ເພື່ອບັນທຶກສະບັບປັດຈຸບັນນີ້."
+
+msgid "Compare"
+msgstr "ສົມທຽບ"
+
+#, fuzzy
+msgid "Compare: %1"
+msgstr "ສົມທຽບ"
+
+msgid "Write Anyway"
+msgstr "ຂຽນລົງເລີຍ"
+
+msgid "Application Error"
+msgstr "ໂປຣແກຣມຜິດພາດ"
+
+msgid "Report Bug"
+msgstr "ລາຍງານຂໍ້ຜິດພາດ"
+
+#, fuzzy
+msgid "Processing"
+msgstr "ກຳລັງປະມວນຜົນ..."
+
+msgid "Search..."
+msgstr "ຄົ້ນຫາ..."
+
+msgid "Create..."
+msgstr "ສ້າງ..."
+
+msgid "Download"
+msgstr "ດາວໂຫຼດ"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "ໂຕໜັງສື"
+
+msgid "Size"
+msgstr "ຂະໜາດ"
+
+msgid "Switch view"
+msgstr "ສະຫຼັບໜ້າເບິ່ງ"
+
+msgid "Previous Record"
+msgstr "ແຖວຂໍ້ມູນກ່ອນ"
+
+msgid "Next Record"
+msgstr "ແຖວຂໍ້ມູນຕໍ່ໄປ"
+
+msgid "Create a new record"
+msgstr "ສ້າງແຖວຂໍ້ມູນໃໝ່"
+
+msgid "Save this record"
+msgstr "ບັນທຶກແຖວຂໍ້ມູນນີ້"
+
+msgid "Reload"
+msgstr "ໂຫຼດຄືນໃໝ່"
+
+msgid "View Logs..."
+msgstr "ເບິ່ງບັນທຶກ..."
+
+msgid "Show revisions..."
+msgstr "ສະແດງການແກ້ໄຂ"
+
+msgid "Add an attachment to the record"
+msgstr "ເອົາຂໍ້ມູນຄັດຕິດເຂົ້າໃນແຖວຂໍ້ມູນນີ້"
+
+msgid "Add a note to the record"
+msgstr "ບັນທຶກຂໍ້ຄວາມໃສ່ແຖວຂໍ້ມູນນີ້"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "ບັນທຶກຂໍ້ຄວາມໃສ່ແຖວຂໍ້ມູນນີ້"
+
+msgid "Export"
+msgstr "ສ່ົງອອກ"
+
+msgid "Import"
+msgstr "ນຳເຂົ້າ"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"ການດຳເນີນການຕໍ່ໄປນີ້ ຮຽກຮ້ອງໃຫ້ຕ້ອງອັດແຖບງານທັງໝົດລົງ\n"
+"ເຈົ້າຕ້ອງການດຳເນີນຕໍ່ບໍ່?"
+
+msgid "Launch action"
+msgstr "ປະຕິບັດການ"
+
+msgid "Open related records"
+msgstr "ໄຂແຖວຂໍ້ມູນທີ່ກ່ຽວພັນຂຶ້ນມາ"
+
+msgid "Print report"
+msgstr "ພິມລາຍງານອອກ"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"ແຖວຂໍ້ມູນນີ້ຖືກປ່ຽນແປງແລ້ວ\n"
+"ທ່ານຕ້ອງການບັນທຶກໄວ້ບໍ່?"
+
+msgid "Record saved."
+msgstr "ແຖວຂໍ້ມູນຖືກບັນທຶກໄວ້ແລ້ວ"
+
+msgid "Working now on the duplicated record(s)."
+msgstr "ປັດຈຸບັນເຮັດວຽກກັບແຖວຂໍ້ມູນທີ່ສ້າງຊໍ້າກັນໄວ້"
+
+msgid "Are you sure to remove this record?"
+msgstr "ທ່ານແນ່ໃຈບໍ່ທີ່ຈະລຶບແຖວຂໍ້ມູນນີ້?"
+
+msgid "Are you sure to remove those records?"
+msgstr "ທ່ານແນ່ໃຈບໍ່ທີ່ຈະລຶບແຖວຂໍ້ມູນເຫຼົ່ານີ້?"
+
+msgid "Records removed."
+msgstr "ແຖວຂໍ້ມູນເຫຼົ່ານີ້ຖືກລຶບອອກແລ້ວ"
+
+msgid "Records not removed."
+msgstr "ແຖວຂໍ້ມູນເຫຼົ່ານີ້ບໍ່ໄດ້ຖືກລຶບອອກ"
+
+msgid "You have to select one record."
+msgstr "ທ່ານຕ້ອງເລືອກເອົາໜຶ່ງແຖວຂໍ້ມູນ"
+
+msgid "ID:"
+msgstr "ເລກກຳກັບ:"
+
+#, fuzzy
+msgid "Created by:"
+msgstr "ສ້າງ..."
+
+#, fuzzy
+msgid "Created at:"
+msgstr "ວັນທີສ້າງ"
+
+#, fuzzy
+msgid "Edited by:"
+msgstr "ສ້າງ..."
+
+#, fuzzy
+msgid "Edited at:"
+msgstr "ວັນທີແກ້ໄຂ"
+
+msgid "Model: "
+msgstr "ແມ່ແບບ: "
+
+msgid "Add..."
+msgstr ""
+
+#, fuzzy
+msgid "Preview"
+msgstr "ກ່ອນໜ້າ"
+
+#, fuzzy
+msgid "Template"
+msgstr "ກ່ຽວພັນກັບ"
+
+#, fuzzy
+msgid "Attachment (%1)"
+msgstr "ຄັດຕິດ(%1)"
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr " ຂອງ "
+
+msgid "Filters"
+msgstr "ກັ່ນຕອງ"
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr "ໝາຍໜ້າ"
+
+msgid "Remove this bookmark"
+msgstr "ເອົາໝາຍໜ້າອອກ"
+
+msgid "Bookmark this filter"
+msgstr "ໝາຍໜ້າເອົາການກັ່ນຕອງນີ້"
+
+msgid "Bookmark Name:"
+msgstr "ຊື່ໝາຍໜ້າ:"
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr "ຄົ້ນຫາ"
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr "ເລກລຳດັບ"
+
+#, fuzzy
+msgid "Created by"
+msgstr "ສ້າງ..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "ວັນທີສ້າງ"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "ວັນທີແກ້ໄຂ"
+
+#, fuzzy
+msgid "\"%1\" is required."
+msgstr "ຕ້ອງໃຫ້ມີ \"%1\""
+
+#, fuzzy
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" ບໍ່ມີຄ່າທີ່ຖຶກຕ້ອງກັບ ໂດເມນຂອງມັນ"
+
+#, fuzzy
+msgid "The values of \"%1\" are not valid."
+msgstr "ຄ່າ ຂອງ \"%1\" ນີ້ ບໍ່ຖືກຕ້ອງ"
+
+msgid "Pre-validation"
+msgstr "ກ່ອນ-ການກວດສອບ"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "ແປ"
+
+msgid "Edit"
+msgstr "ແກ້ໄຂ"
+
+msgid "Fuzzy"
+msgstr "ບໍ່ຈະແຈ້ງ"
+
+msgid "You need to save the record before adding translations."
+msgstr "ທ່ານຕ້ອງໄດ້ບັນທຶກແຖວຂໍ້ມູນໄວ້ກ່ອນຈຶ່ງຈະເພີ່ມການແປເຂົ້າໄປໄດ້"
+
+msgid "No other language available."
+msgstr "ບໍ່ມີພາສາອື່ນໃຫ້ໃຊ້ໄດ້."
+
+msgid "Show"
+msgstr "ສະແດງ"
+
+msgid "Hide"
+msgstr "ເຊື່ອງ"
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr "ເອົາເຂົ້າ"
+
+msgid "Remove"
+msgstr "ເອົາອອກ"
+
+msgid "Open"
+msgstr "ໄຂ"
+
+msgid "Undelete"
+msgstr "ເອົາກັບຄືນ"
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr "ເລືອກ"
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr "ລຶບອອກ"
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "ເພີ່ມຕື່ມ"
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "ເລືອກການດຳເນີນການ"
+
+msgid "No action defined."
+msgstr "ບໍ່ມີການດຳເນີນການໃດຖືກກຳນົດໄວ້!"
+
+msgid "Attachments (%1)"
+msgstr "ຄັດຕິດ(%1)"
+
+msgid "Notes (%1)"
+msgstr "ບັນທຶກຂໍ້ຄວາມ(%1)"
+
+msgid "Search %1"
+msgstr "ຊອກຫາ %1"
+
+msgid "Revision"
+msgstr "ກວດກາຄືນ"
+
+msgid "All Fields"
+msgstr "ຊ່ອງຂໍ້ມູນທັງໝົດ"
+
+msgid "Fields Selected"
+msgstr "ຊ່ອງຂໍ້ມູນທີ່ເລືອກເອົາ"
+
+msgid "CSV Parameters"
+msgstr "ປາຣາແມັດເຕີຣ໌ CSV"
+
+msgid "Delimiter:"
+msgstr "ຂັ້ນ:"
+
+msgid "Quote Char:"
+msgstr "Quote Char:"
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr "ກວດພົບອັດຕະໂນມັດ"
+
+msgid "File to Import"
+msgstr "ຟາຍລ໌ທີ່ຈະນຳເຂົ້າ"
+
+msgid "Encoding:"
+msgstr "ການເຂົ້າລະຫັດ:"
+
+msgid "Lines to Skip:"
+msgstr "ແຖວທີ່ຈະຂວ້າມ:"
+
+#, fuzzy
+msgid "You must select an import file first."
+msgstr "ທ່ານຕ້ອງໄດ້ເລືອກຟາຍລ໌ທີ່ຈະນຳເຂົ້ານັ້ນກ່ອນ"
+
+#, fuzzy
+msgid "An error occured in loading the file."
+msgstr "ມີຂໍ້ຜິດພາດເກີດຂຶ້ນໃນການດຶງເອົາຟາຍລ໌"
+
+msgid "Error processing the file at field %1."
+msgstr "ມີຂໍ້ຜິດພາດໃນການປະມວນຜົນຟາຍລ໌ ໃນຊ່ອງຂໍ້ມູນ %1."
+
+msgid "Error"
+msgstr "ຜິດພາດ"
+
+msgid "Error occured in loading the file"
+msgstr "ມີຂໍ້ຜິດພາດເກີດຂຶ້ນໃນການດຶງເອົາຟາຍລ໌"
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr "ບັນທຶກຟາຍລ໌ທີ່ສົ່ງອອກ"
+
+#, fuzzy
+msgid "URL Export"
+msgstr "ສ່ົງອອກ"
+
+msgid "Delete Export"
+msgstr "ລຶບຟາຍລ໌ທີ່ສົ່ງອອກ"
+
+msgid "Predefined Exports"
+msgstr "ການສົ່ງອອກທີ່ຈັດຕຽມໄວ້ກ່ອນ"
+
+#, fuzzy
+msgid "Selected Records"
+msgstr "ແຖວຂໍ້ມູນຕໍ່ໄປ"
+
+#, fuzzy
+msgid "Listed Records"
+msgstr "ແຖວຂໍ້ມູນຕໍ່ໄປ"
+
+#, fuzzy
+msgid "Export:"
+msgstr "ສ່ົງອອກ"
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr "ເພີ່ມຊື່ຊ່ອງຂໍ້ມູນ"
+
+msgid "%1 (string)"
+msgstr "%1 (string)"
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr "ຊື່ຂອງການສົ່ງອອກນີ້ແມ່ນຫຍັງ?"
+
+msgid "Override %1 definition?"
+msgstr "ຂຽນທັບຄຳອະທິບາຍ %1 ນີ້ບໍ?"
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "ລາຍງານຂໍ້ຜິດພາດ"
+
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-05-01 15:33+0000\n"
+"PO-Revision-Date: 2019-10-31 09:05+0000\n"
+"Last-Translator: Cedric Krier \n"
+"Language: lo\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Weblate 3.8\n"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "ຄັດຕິດ"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+#, fuzzy
+msgid "Remove File"
+msgstr "ເອົາອອກ"
+
+msgid "Wizard"
+msgstr "ນໍາພາສ້າງ"
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/lt.po b/sao/locale/lt.po
new file mode 100644
index 0000000000..a401085a61
--- /dev/null
+++ b/sao/locale/lt.po
@@ -0,0 +1,661 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr "Mėgstami"
+
+msgid "Manage..."
+msgstr "Valdyti..."
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr "Veiksmai"
+
+msgid "New"
+msgstr "Naujas"
+
+msgid "Save"
+msgstr "Išsaugoti"
+
+msgid "Switch"
+msgstr "Perjungti"
+
+msgid "Reload/Undo"
+msgstr "Perkrauti/Atšaukti"
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr "Naikinti"
+
+msgid "Previous"
+msgstr "Buvęs"
+
+msgid "Next"
+msgstr "Kitas"
+
+msgid "Search"
+msgstr "Ieškoti"
+
+msgid "Close Tab"
+msgstr "Uždaryti kortelę"
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr "Pastaba"
+
+msgid "Relate"
+msgstr "Susijęs"
+
+msgid "Print"
+msgstr "Spausdinimas"
+
+msgid "E-Mail"
+msgstr ""
+
+#, fuzzy
+msgid "Previous tab"
+msgstr "_Buvusi kortelė"
+
+#, fuzzy
+msgid "Next tab"
+msgstr "Kita kortelė"
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr "Užverti"
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "Prisijungti"
+
+msgid "User name"
+msgstr "Naudotojo vardas"
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr "Jūsų pasirinkimas:"
+
+msgid "Cancel"
+msgstr "Atsisakyti"
+
+msgid "OK"
+msgstr "Gerai"
+
+msgid "Y"
+msgstr "m."
+
+msgid "M"
+msgstr "mėn."
+
+msgid "w"
+msgstr "sav."
+
+msgid "d"
+msgstr "d."
+
+msgid "h"
+msgstr "val."
+
+msgid "m"
+msgstr "min."
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr ""
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "t"
+
+msgid "Yes"
+msgstr "Taip"
+
+msgid "True"
+msgstr "Tiesa"
+
+msgid "t"
+msgstr "t"
+
+msgid "False"
+msgstr "Netiesa"
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr "Daugiau nepaisyti šio pranešimo."
+
+msgid "Do you want to proceed?"
+msgstr "Ar tikrai norite vykdyti?"
+
+msgid "No"
+msgstr "Ne"
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr "Lygiagretaus duomenų rašymo klaida"
+
+msgid "Write Concurrency Warning: "
+msgstr "Lygiagretaus duomenų rašymo perspėjimas: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Kol jūs taisėte šį įrašą, jis buvo pakeistas."
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr "Palyginti"
+
+#, fuzzy
+msgid "Compare: %1"
+msgstr "Palyginti"
+
+msgid "Write Anyway"
+msgstr "Rašyti visvien"
+
+msgid "Application Error"
+msgstr "Programos klaida"
+
+msgid "Report Bug"
+msgstr "Pranešti apie klaidą"
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr "Atsisiųsti"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr "Perjungti rodymą"
+
+msgid "Previous Record"
+msgstr "Buvęs įrašas"
+
+msgid "Next Record"
+msgstr "Kitas įrašas"
+
+msgid "Create a new record"
+msgstr "Sukurti naują įrašą"
+
+msgid "Save this record"
+msgstr "Išsaugoti šį įrašą"
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr "Rodyti pakeitimus..."
+
+msgid "Add an attachment to the record"
+msgstr "Prisegti failą prie įrašo"
+
+msgid "Add a note to the record"
+msgstr "Prisegti pastabą prie įrašo"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "Prisegti pastabą prie įrašo"
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Atliekant šį veiksmą bus uždarytos visos kortelės.\n"
+"Ar norite tęsti?"
+
+msgid "Launch action"
+msgstr "Vykdyti veiksmą"
+
+msgid "Open related records"
+msgstr "Atverti susijusius įrašus"
+
+msgid "Print report"
+msgstr "Spausdinti ataskaitą"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Šis įrašas buvo pakeistas\n"
+"Ar norite jį išsaugoti?"
+
+msgid "Record saved."
+msgstr "Įrašas išsaugotas."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Dabar dirbate su įrašo(-ų) kopija(-omis)."
+
+msgid "Are you sure to remove this record?"
+msgstr "Ar tikrai norite ištrinti šį įrašą?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Ar tikrai norite ištrinti šiuos įrašus?"
+
+msgid "Records removed."
+msgstr "Įrašai ištrinti."
+
+msgid "Records not removed."
+msgstr "Įrašai nebuvo ištrinti."
+
+msgid "You have to select one record."
+msgstr "Pasirinkite bent vieną įrašą."
+
+msgid "ID:"
+msgstr "ID:"
+
+#, fuzzy
+msgid "Created by:"
+msgstr "Sukūręs naudotojas"
+
+#, fuzzy
+msgid "Created at:"
+msgstr "Sukūrimo data"
+
+msgid "Edited by:"
+msgstr "Pakeitęs naudotojas:"
+
+msgid "Edited at:"
+msgstr "Keitimo data:"
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr "Sukurti naują..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "Buvęs"
+
+#, fuzzy
+msgid "Template"
+msgstr "Susijęs"
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr " iš "
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr "Šalinti šią žymelę"
+
+msgid "Bookmark this filter"
+msgstr "Sukurti šio filtro žymelę"
+
+msgid "Bookmark Name:"
+msgstr "Žymelės pavadinimas:"
+
+msgid "Show active records"
+msgstr "Rodyti aktyvius įrašus"
+
+msgid "Show inactive records"
+msgstr "Rodyti neaktyvius įrašus"
+
+msgid "Find"
+msgstr "Rasti"
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr "Atverti kalendorių"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Sukūręs naudotojas"
+
+msgid "Created at"
+msgstr "Sukūrimo data"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "Pakeitęs naudotojas"
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr "Išankstinis patikrinimas"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr "Keisti"
+
+msgid "Fuzzy"
+msgstr "Netikslus"
+
+msgid "You need to save the record before adding translations."
+msgstr "Išsaugokite įrašą prieš pridedant vertimą."
+
+msgid "No other language available."
+msgstr "Nėra kitų kalbų."
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr "Pridėti"
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr "Atvėrimas"
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr "Įrašyti kaip"
+
+msgid "Save As..."
+msgstr "Įrašyti kaip..."
+
+msgid "Select"
+msgstr "Pasirinkti"
+
+msgid "Select..."
+msgstr "Pasirinkti..."
+
+msgid "Clear"
+msgstr "Valyti"
+
+msgid "Choose a language"
+msgstr "Pasirinkti kalbą"
+
+msgid "%1%"
+msgstr ""
+
+#, fuzzy
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr "Šiandien"
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr "Savaitė"
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr "Pasirinkite veiksmą"
+
+msgid "No action defined."
+msgstr "Nėra apibrėžtų veiksmų."
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr "Pastabos (%1)"
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr "Taisymas"
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr "CSV parametrai"
+
+msgid "Delimiter:"
+msgstr "Skirtukas:"
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr "Koduotė:"
+
+msgid "Lines to Skip:"
+msgstr "Praleisti eilutes:"
+
+msgid "You must select an import file first."
+msgstr "Jūs pirmiausiai turite pasirinkti importo failą."
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr "Klaida"
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr "Pasirinkti įrašai"
+
+msgid "Listed Records"
+msgstr "Rodomi įrašai"
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr "Naudoti lokalės formatą"
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr "Kokiu pavadinimu išsaugoti šį eksportavimo šabloną?"
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "Pranešti apie klaidą"
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr "Vedlys"
+
+msgid "Translate view"
+msgstr "Išversti rodinį"
diff --git a/sao/locale/messages.pot b/sao/locale/messages.pot
new file mode 100644
index 0000000000..c49d77e2af
--- /dev/null
+++ b/sao/locale/messages.pot
@@ -0,0 +1,643 @@
+# Generated by grunt-xgettext on Sat May 01 2021 02:11:54 GMT+0200 (Central European Summer Time)
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+msgid ", "
+msgstr ""
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "..."
+msgstr ""
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid "The following action requires to close all tabs.\nDo you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid "This record has been modified\ndo you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr ""
+
+msgid "Created at:"
+msgstr ""
+
+msgid "Edited by:"
+msgstr ""
+
+msgid "Edited at:"
+msgstr ""
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr ""
+
+msgid "Created at"
+msgstr ""
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ""
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+msgid ": "
+msgstr ""
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid ""
+msgstr ""
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/nl.po b/sao/locale/nl.po
new file mode 100644
index 0000000000..b8d0e7f1cd
--- /dev/null
+++ b/sao/locale/nl.po
@@ -0,0 +1,652 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "Ben je zeker dat je weggaat?"
+
+msgid "Favorites"
+msgstr "Favorieten"
+
+msgid "Manage..."
+msgstr "Beheren..."
+
+msgid "Logout"
+msgstr "Uitloggen"
+
+msgid "Action"
+msgstr "Actie"
+
+msgid "New"
+msgstr "Nieuw"
+
+msgid "Save"
+msgstr "Opslaan"
+
+msgid "Switch"
+msgstr "Omschakelen"
+
+msgid "Reload/Undo"
+msgstr "Herladen/Annuleren"
+
+msgid "Duplicate"
+msgstr "Duplicaat"
+
+msgid "Delete"
+msgstr "Verwijderen"
+
+msgid "Previous"
+msgstr "Vorige"
+
+msgid "Next"
+msgstr "Volgende"
+
+msgid "Search"
+msgstr "Zoeken"
+
+msgid "Close Tab"
+msgstr "Tabblad sluiten"
+
+msgid "Attachment"
+msgstr "Bijlage"
+
+msgid "Note"
+msgstr "Aantekening"
+
+msgid "Relate"
+msgstr "Relatie"
+
+msgid "Print"
+msgstr "Afdrukken"
+
+msgid "E-Mail"
+msgstr "E-mail"
+
+msgid "Previous tab"
+msgstr "Vorig tabblad"
+
+msgid "Next tab"
+msgstr "Volgende tabblad"
+
+msgid "Global search"
+msgstr "Algemeen zoeken"
+
+msgid "Show this help"
+msgstr "Toon deze hulp"
+
+msgid "Keyboard shortcuts"
+msgstr "Toetsenbord sneltoetsen"
+
+msgid "Close"
+msgstr "Sluiten"
+
+msgid "Global shortcuts"
+msgstr "algemene snelkoppeling"
+
+msgid "Tab shortcuts"
+msgstr "Tab snelkoppelingen"
+
+msgid "Unable to reach the server."
+msgstr "Kan geen verbinding maken met server."
+
+msgid "Too many requests. Try again later."
+msgstr "Te veel verzoeken. Probeer het later nog eens."
+
+msgid "Login"
+msgstr "Inlognaam"
+
+msgid "User name"
+msgstr "Gebruikersnaam"
+
+msgid "Database"
+msgstr "Database"
+
+msgid "Your selection:"
+msgstr "Uw selectie:"
+
+msgid "Cancel"
+msgstr "Annuleren"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "J"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "w"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Door: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "J"
+
+msgid "Yes"
+msgstr "Ja"
+
+msgid "True"
+msgstr "Waar"
+
+msgid "t"
+msgstr "w (t)"
+
+msgid "False"
+msgstr "Niet waar"
+
+msgid "Message: "
+msgstr "Bericht: "
+
+msgid "Warning: "
+msgstr "Waarschuwing: "
+
+msgid "Always ignore this warning."
+msgstr "Deze waarschuwing altijd negeren."
+
+msgid "Do you want to proceed?"
+msgstr "Doorgaan?"
+
+msgid "No"
+msgstr "Nee"
+
+msgid "Confirmation: "
+msgstr "Bevestiging: "
+
+msgid "Concurrency Exception"
+msgstr "Simultaan gebruik (error)"
+
+msgid "Write Concurrency Warning: "
+msgstr "Aandacht, schrijftoegang concurrenten (Write Concurrency) : "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Dit veld werd gewijzigd terwijl u het aan het bewerken was."
+
+msgid "Choose:"
+msgstr "Kiezen:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Annuleren\" om het opslaan te annuleren;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Vergelijken\" om de gewijzigde versie te zien;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Overschrijven\" om de huidige versie te bewaren."
+
+msgid "Compare"
+msgstr "Vergelijken"
+
+msgid "Compare: %1"
+msgstr "Vergelijk:% 1"
+
+msgid "Write Anyway"
+msgstr "Overschrijven"
+
+msgid "Application Error"
+msgstr "Applicatiefout"
+
+msgid "Report Bug"
+msgstr "Programmafout melden"
+
+msgid "Processing"
+msgstr "Verwerking"
+
+msgid "Search..."
+msgstr "Zoeken..."
+
+msgid "Create..."
+msgstr "Maak ..."
+
+msgid "Download"
+msgstr "Download"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "lettertype"
+
+msgid "Size"
+msgstr "Grootte"
+
+msgid "Switch view"
+msgstr "Wissel van weergave"
+
+msgid "Previous Record"
+msgstr "Vorig record"
+
+msgid "Next Record"
+msgstr "Volgende record"
+
+msgid "Create a new record"
+msgstr "Maak een nieuw record"
+
+msgid "Save this record"
+msgstr "Bewaar deze record"
+
+msgid "Reload"
+msgstr "Herladen"
+
+msgid "View Logs..."
+msgstr "Bekijk logregister..."
+
+msgid "Show revisions..."
+msgstr "Herzieningen tonen ..."
+
+msgid "Add an attachment to the record"
+msgstr "Voeg een bijlage toe aan het record"
+
+msgid "Add a note to the record"
+msgstr "Voeg een nota toe aan het veld"
+
+msgid "E-Mail..."
+msgstr "E-mail..."
+
+msgid "Send an e-mail using the record"
+msgstr "Stuur een e-mail met behulp van het record"
+
+msgid "Export"
+msgstr "Exporteren"
+
+msgid "Import"
+msgstr "Importeren"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"De volgende actie vereist het sluiten van alle tabbladen.\n"
+"Verder gaan?"
+
+msgid "Launch action"
+msgstr "Start actie"
+
+msgid "Open related records"
+msgstr "Open gerelateerde records"
+
+msgid "Print report"
+msgstr "Rapport afdrukken"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Dit veld is aangepast.\n"
+"Wil je de wijzigingen opslaan?"
+
+msgid "Record saved."
+msgstr "Record opgeslagen."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Nu aan het werk op het/de gecopieërde record(s)."
+
+msgid "Are you sure to remove this record?"
+msgstr "Weet je zeker dat je dit record wilt verwijderen?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Weet je zeker dat je deze records wilt verwijderen?"
+
+msgid "Records removed."
+msgstr "Records verwijderd."
+
+msgid "Records not removed."
+msgstr "Records niet verwijderd."
+
+msgid "You have to select one record."
+msgstr "U moet één record selecteren."
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Aangemaakt door:"
+
+msgid "Created at:"
+msgstr "Aanmaakdatum:"
+
+msgid "Edited by:"
+msgstr "Aangepast door:"
+
+msgid "Edited at:"
+msgstr "Bewerkt op:"
+
+msgid "Model: "
+msgstr "Model: "
+
+msgid "Add..."
+msgstr "Toevoegen..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "Vorige"
+
+msgid "Template"
+msgstr "Sjabloon"
+
+msgid "Attachment (%1)"
+msgstr "Bijlage (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Opmerking (%1/%2)"
+
+msgid " of "
+msgstr " van "
+
+msgid "Filters"
+msgstr "filters"
+
+msgid "Clear Search"
+msgstr "Wis zoeken"
+
+msgid "Bookmarks"
+msgstr "bladwijzers"
+
+msgid "Remove this bookmark"
+msgstr "Verwijder deze bladwijzer"
+
+msgid "Bookmark this filter"
+msgstr "Maak een bladwijzer van deze filter"
+
+msgid "Bookmark Name:"
+msgstr "Bladwijzernaam:"
+
+msgid "Show active records"
+msgstr "Toon actieve records"
+
+msgid "Show inactive records"
+msgstr "Toon niet actieve records"
+
+msgid "Find"
+msgstr "Zoeken"
+
+msgid "From"
+msgstr "Van"
+
+msgid "To"
+msgstr "Aan"
+
+msgid "Open the calendar"
+msgstr "Open de kalender"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Aangemaakt door"
+
+msgid "Created at"
+msgstr "Aanmaakdatum"
+
+msgid "Modified by"
+msgstr "Aangepast door"
+
+msgid "Modified at"
+msgstr "Gewijzigd om"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" is vereist."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" is niet geldig volgens zijn domein."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "De waarden van \"%1\" zijn niet geldig."
+
+msgid "Pre-validation"
+msgstr "Pre-validatie"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Vertalen"
+
+msgid "Edit"
+msgstr "Bewerk"
+
+msgid "Fuzzy"
+msgstr "Flauwe"
+
+msgid "You need to save the record before adding translations."
+msgstr "U moet de record opslaan voordat u vertalingen toevoegt."
+
+msgid "No other language available."
+msgstr "Geen andere taal beschikbaar."
+
+msgid "Show"
+msgstr "Toon"
+
+msgid "Hide"
+msgstr "Verbergen"
+
+msgid "Open the record"
+msgstr "Open het record"
+
+msgid "Clear the field"
+msgstr "Wis het veld"
+
+msgid "Search a record"
+msgstr "Zoek een record"
+
+msgid "Add"
+msgstr "Toevoegen"
+
+msgid "Remove"
+msgstr "Verwijderen"
+
+msgid "Open"
+msgstr "Openen"
+
+msgid "Undelete"
+msgstr "wissen ongedaan maken"
+
+msgid "Save As"
+msgstr "Opslaan als"
+
+msgid "Save As..."
+msgstr "Opslaan als..."
+
+msgid "Select"
+msgstr "Selecteer"
+
+msgid "Select..."
+msgstr "Selecteer ..."
+
+msgid "Clear"
+msgstr "Wissen"
+
+msgid "Choose a language"
+msgstr "Kies een taal"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Meer"
+
+msgid "Today"
+msgstr "Vandaag"
+
+msgid "Month"
+msgstr "Maand"
+
+msgid "Week"
+msgstr "Week"
+
+msgid "Day"
+msgstr "Dag"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Selecteer je handeling"
+
+msgid "No action defined."
+msgstr "Geen handeling gedefinieerd."
+
+msgid "Attachments (%1)"
+msgstr "Bijlagen (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notities (%1)"
+
+msgid "Search %1"
+msgstr "Zoek %1"
+
+msgid "Revision"
+msgstr "Herziening"
+
+msgid "All Fields"
+msgstr "Alle velden"
+
+msgid "Fields Selected"
+msgstr "Geselecteerde Velden"
+
+msgid "CSV Parameters"
+msgstr "CSV parameters"
+
+msgid "Delimiter:"
+msgstr "scheidingsteken:"
+
+msgid "Quote Char:"
+msgstr "aanhalingstekens :"
+
+msgid "CSV Import: %1"
+msgstr "CSV-Import: %1"
+
+msgid "Auto-Detect"
+msgstr "Automatische detectie"
+
+msgid "File to Import"
+msgstr "Bestand om te importeren"
+
+msgid "Encoding:"
+msgstr "Codering:"
+
+msgid "Lines to Skip:"
+msgstr "Regels overslaan:"
+
+msgid "You must select an import file first."
+msgstr "U moet eerst een importbestand selecteren."
+
+msgid "An error occured in loading the file."
+msgstr "Er is een fout opgetreden bij het laden van het bestand."
+
+msgid "Error processing the file at field %1."
+msgstr "Fout bij verwerken van het bestand op veld %1."
+
+msgid "Error"
+msgstr "Fout"
+
+msgid "Error occured in loading the file"
+msgstr "Er is een fout opgetreden bij het laden van het bestand"
+
+msgid "CSV Export: %1"
+msgstr "Exporteer CSV: %1"
+
+msgid "Save Export"
+msgstr "Export opslaan"
+
+msgid "URL Export"
+msgstr "URL Exporteren"
+
+msgid "Delete Export"
+msgstr "Export verwijderen"
+
+msgid "Predefined Exports"
+msgstr "Vooraf gedefinieerde exports"
+
+msgid "Selected Records"
+msgstr "Geselecteerde records"
+
+msgid "Listed Records"
+msgstr "Vermelde records"
+
+msgid "Export:"
+msgstr "Exporteer:"
+
+msgid "Ignore search limit"
+msgstr "Zoeklimiet negeren"
+
+msgid "Use locale format"
+msgstr "Gebruik lokaal formaat"
+
+msgid "Add Field Names"
+msgstr "Voeg veldnamen toe"
+
+msgid "%1 (string)"
+msgstr "%1 (string)"
+
+msgid "%1 (model name)"
+msgstr "%1 (model naam)"
+
+msgid "%1 (record name)"
+msgstr "%1 (recordnaam)"
+
+msgid "What is the name of this export?"
+msgstr "Wat is de naam voor deze export?"
+
+msgid "Override %1 definition?"
+msgstr "Definitie van %1 overschrijven?"
+
+msgid "E-mail %1"
+msgstr "E-mail %1"
+
+msgid "To:"
+msgstr "Aan:"
+
+msgid "Cc:"
+msgstr "Cc:"
+
+msgid "Bcc:"
+msgstr "Bcc:"
+
+msgid "Subject:"
+msgstr "Onderwerp:"
+
+msgid "Reports"
+msgstr "Rapporten"
+
+msgid "Attachments"
+msgstr "Bijlagen"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr "Sturen"
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Verwijderen"
+
+msgid "Wizard"
+msgstr "Assistent"
+
+msgid "Translate view"
+msgstr "Vertaal aanzicht"
diff --git a/sao/locale/pl.po b/sao/locale/pl.po
new file mode 100644
index 0000000000..14d060c547
--- /dev/null
+++ b/sao/locale/pl.po
@@ -0,0 +1,666 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Apr 06 2018 22:57:49 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Oct 09 2017 19:35:04 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "Czy na pewno wyjść?"
+
+msgid "Favorites"
+msgstr "Ulubione"
+
+msgid "Manage..."
+msgstr "Zarządzaj..."
+
+msgid "Logout"
+msgstr "Wyloguj"
+
+msgid "Action"
+msgstr "Akcja"
+
+msgid "New"
+msgstr "Nowy"
+
+msgid "Save"
+msgstr "Zapisz"
+
+msgid "Switch"
+msgstr "Przełącz"
+
+msgid "Reload/Undo"
+msgstr "Odśwież/Przywróć"
+
+msgid "Duplicate"
+msgstr "Duplikuj"
+
+msgid "Delete"
+msgstr "Usuń"
+
+msgid "Previous"
+msgstr "Poprzedni"
+
+msgid "Next"
+msgstr "Następny"
+
+msgid "Search"
+msgstr "Szukaj"
+
+msgid "Close Tab"
+msgstr "Zamknij kartę"
+
+msgid "Attachment"
+msgstr "Załącznik"
+
+msgid "Note"
+msgstr "Notatka"
+
+msgid "Relate"
+msgstr "Odnośnik"
+
+msgid "Print"
+msgstr "Wydruk"
+
+msgid "E-Mail"
+msgstr "E-mail"
+
+msgid "Previous tab"
+msgstr "Poprzednia karta"
+
+msgid "Next tab"
+msgstr "Następna karta"
+
+msgid "Global search"
+msgstr "Wyszukiwanie globalne"
+
+msgid "Show this help"
+msgstr "Wyświetl tę pomoc"
+
+msgid "Keyboard shortcuts"
+msgstr "Skróty klawiszowe"
+
+msgid "Close"
+msgstr "Zamknij"
+
+msgid "Global shortcuts"
+msgstr "Skróty globalne"
+
+msgid "Tab shortcuts"
+msgstr "Skróty kart"
+
+msgid "Unable to reach the server."
+msgstr "Brak dostępu do serwera."
+
+msgid "Too many requests. Try again later."
+msgstr "Za dużo połączeń. Spróbuj później."
+
+msgid "Login"
+msgstr "Login"
+
+msgid "User name"
+msgstr "Nazwa użytkownika"
+
+msgid "Database"
+msgstr "Baza danych"
+
+msgid "Your selection:"
+msgstr "Twoje zaznaczenie:"
+
+msgid "Cancel"
+msgstr "Anuluj"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "R"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "t"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "g"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Przez: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "t"
+
+msgid "Yes"
+msgstr "Tak"
+
+msgid "True"
+msgstr "Tak"
+
+msgid "t"
+msgstr "p"
+
+msgid "False"
+msgstr "Nie"
+
+msgid "Message: "
+msgstr "Wiadomość: "
+
+msgid "Warning: "
+msgstr "Ostrzeżenie: "
+
+msgid "Always ignore this warning."
+msgstr "Zawsze ignoruj takie ostrzeżenie."
+
+msgid "Do you want to proceed?"
+msgstr "Czy chcesz kontynuować?"
+
+msgid "No"
+msgstr "Nie"
+
+msgid "Confirmation: "
+msgstr "Potwierdzenie: "
+
+msgid "Concurrency Exception"
+msgstr "Wyjątek równoczesnego zapisu danych"
+
+msgid "Write Concurrency Warning: "
+msgstr "Ostrzeżenie o równoczesnym zapisie danych: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Ten rekord został zmodyfikowany w trakcie twojej edycji."
+
+msgid "Choose:"
+msgstr "Wybierz:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Anuluj\" jeśli chcesz anulować zapis danych;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Porównaj\" jeśli chcesz zobaczyć zmienioną wersję danych;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Zapisz\" jeśli chcesz zapisać aktualną wersję danych."
+
+msgid "Compare"
+msgstr "Porównaj"
+
+msgid "Compare: %1"
+msgstr "Porównaj: %1"
+
+msgid "Write Anyway"
+msgstr "Zapisz"
+
+msgid "Application Error"
+msgstr "Błąd aplikacji"
+
+msgid "Report Bug"
+msgstr "Zgłoś błąd"
+
+msgid "Processing"
+msgstr "Przetwarzanie"
+
+msgid "Search..."
+msgstr "Szukaj..."
+
+msgid "Create..."
+msgstr "Utwórz..."
+
+msgid "Download"
+msgstr "Pobierz"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Czcionka"
+
+msgid "Size"
+msgstr "Rozmiar"
+
+msgid "Switch view"
+msgstr "Przełącz widok"
+
+msgid "Previous Record"
+msgstr "Poprzedni rekord"
+
+msgid "Next Record"
+msgstr "Następny rekord"
+
+msgid "Create a new record"
+msgstr "Utwórz nowy rekord"
+
+msgid "Save this record"
+msgstr "Zapisz rekord"
+
+msgid "Reload"
+msgstr "Odśwież"
+
+msgid "View Logs..."
+msgstr "Pokaż dziennik..."
+
+msgid "Show revisions..."
+msgstr "Pokaż rewizje..."
+
+msgid "Add an attachment to the record"
+msgstr "Dodaj załącznik do rekordu"
+
+msgid "Add a note to the record"
+msgstr "Dodaj notatkę do rekordu"
+
+msgid "E-Mail..."
+msgstr "E-mail..."
+
+msgid "Send an e-mail using the record"
+msgstr "Wyślij e-mail używając rekordu"
+
+msgid "Export"
+msgstr "Eksportuj"
+
+msgid "Import"
+msgstr "Importuj"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Wykonanie polecenia wymaga zamknięcia wszystkich kart.\n"
+"Czy chcesz kontynuować?"
+
+msgid "Launch action"
+msgstr "Uruchom akcję"
+
+msgid "Open related records"
+msgstr "Otwórz powiązane rekordy"
+
+msgid "Print report"
+msgstr "Drukuj raport"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Rekord został zmodyfikowany.\n"
+"Czy chcesz go zapisać?"
+
+msgid "Record saved."
+msgstr "Rekord został zapisany."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Pracujesz na zduplikowanym(ch) rekordzie(ch)."
+
+msgid "Are you sure to remove this record?"
+msgstr "Czy na pewno usunąć wybrany rekord?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Czy na pewno usunąć wybrane rekordy?"
+
+msgid "Records removed."
+msgstr "Rekordy zostały usunięte."
+
+msgid "Records not removed."
+msgstr "Rekordy nie zostały usunięte."
+
+msgid "You have to select one record."
+msgstr "Musisz wybrać jeden rekord."
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Utworzył:"
+
+msgid "Created at:"
+msgstr "Data utworzenia:"
+
+msgid "Edited by:"
+msgstr "Zmodyfikował:"
+
+msgid "Edited at:"
+msgstr "Data modyfikacji:"
+
+msgid "Model: "
+msgstr "Model: "
+
+msgid "Add..."
+msgstr "Dodaj..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "Poprzedni"
+
+msgid "Template"
+msgstr "Szablon"
+
+msgid "Attachment (%1)"
+msgstr "Załącznik (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Notatka (%1/%2)"
+
+msgid " of "
+msgstr " z "
+
+msgid "Filters"
+msgstr "Filtry"
+
+msgid "Clear Search"
+msgstr "Wyczyść wyszukiwanie"
+
+msgid "Bookmarks"
+msgstr "Zakładki"
+
+msgid "Remove this bookmark"
+msgstr "Usuń zakładkę"
+
+msgid "Bookmark this filter"
+msgstr "Dodaj filtr do zakładek"
+
+msgid "Bookmark Name:"
+msgstr "Dodaj nazwę zakładek:"
+
+msgid "Show active records"
+msgstr "Pokaż aktywne rekordy"
+
+msgid "Show inactive records"
+msgstr "Pokaż nieaktywne rekordy"
+
+msgid "Find"
+msgstr "Znajdź"
+
+msgid "From"
+msgstr "Od"
+
+msgid "To"
+msgstr "Do"
+
+msgid "Open the calendar"
+msgstr "Otwórz kalendarz"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Utworzył"
+
+msgid "Created at"
+msgstr "Data utworzenia"
+
+msgid "Modified by"
+msgstr "Zmodyfikował"
+
+msgid "Modified at"
+msgstr "Data modyfikacji"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" jest wymagany."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" jest nieprawidłowy pod względem swojej domeny."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "Wartości \"%1\" nie są prawidłowe."
+
+msgid "Pre-validation"
+msgstr "Wstępna walidacja"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Przetłumacz"
+
+msgid "Edit"
+msgstr "Edycja"
+
+msgid "Fuzzy"
+msgstr "Niejasne"
+
+msgid "You need to save the record before adding translations."
+msgstr "Musisz zapisać rekord przed dodaniem tłumaczeń."
+
+msgid "No other language available."
+msgstr "Brak innych języków."
+
+msgid "Show"
+msgstr "Pokaż"
+
+msgid "Hide"
+msgstr "Ukryj"
+
+msgid "Open the record"
+msgstr "Otwórz rekord"
+
+msgid "Clear the field"
+msgstr "Wyczyść pole"
+
+msgid "Search a record"
+msgstr "Szukaj rekord"
+
+msgid "Add"
+msgstr "Dodaj"
+
+msgid "Remove"
+msgstr "Usuń"
+
+msgid "Open"
+msgstr "Otwórz"
+
+msgid "Undelete"
+msgstr "Przywróć"
+
+msgid "Save As"
+msgstr "Zapisz jako"
+
+msgid "Save As..."
+msgstr "Zapisz jako..."
+
+msgid "Select"
+msgstr "Wybierz"
+
+msgid "Select..."
+msgstr "Wybierz..."
+
+msgid "Clear"
+msgstr "Wyczyść"
+
+msgid "Choose a language"
+msgstr "Wybierz język"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Więcej"
+
+msgid "Today"
+msgstr "Dzisiaj"
+
+msgid "Month"
+msgstr "Miesiąc"
+
+msgid "Week"
+msgstr "Tydzień"
+
+msgid "Day"
+msgstr "Dzień"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Wybierz akcję"
+
+msgid "No action defined."
+msgstr "Brak zdefiniowanej akcji."
+
+msgid "Attachments (%1)"
+msgstr "Załączniki (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notatki (%1)"
+
+msgid "Search %1"
+msgstr "Szukaj %1"
+
+msgid "Revision"
+msgstr "Rewizja"
+
+msgid "All Fields"
+msgstr "Wszystkie pola"
+
+msgid "Fields Selected"
+msgstr "Pola wybrane"
+
+msgid "CSV Parameters"
+msgstr "Parametry CSV"
+
+msgid "Delimiter:"
+msgstr "Separator:"
+
+msgid "Quote Char:"
+msgstr "Symbol dziesiętny:"
+
+msgid "CSV Import: %1"
+msgstr "Import z CSV: %1"
+
+msgid "Auto-Detect"
+msgstr "Automatyczne rozpoznawanie"
+
+msgid "File to Import"
+msgstr "Plik do zaimportowania"
+
+msgid "Encoding:"
+msgstr "Kodowanie:"
+
+msgid "Lines to Skip:"
+msgstr "Wiersze do ominięcia:"
+
+msgid "You must select an import file first."
+msgstr "Wybierz plik do zaimportowania."
+
+msgid "An error occured in loading the file."
+msgstr "Błąd ładowania pliku."
+
+msgid "Error processing the file at field %1."
+msgstr "Błąd przetwarzania pliku w polu %1."
+
+msgid "Error"
+msgstr "Błąd"
+
+msgid "Error occured in loading the file"
+msgstr "Błąd ładowania pliku"
+
+msgid "CSV Export: %1"
+msgstr "Eksport do CSV: %1"
+
+msgid "Save Export"
+msgstr "Zapisz eksport"
+
+msgid "URL Export"
+msgstr "Eksportuj URL"
+
+msgid "Delete Export"
+msgstr "Usuń eksport"
+
+msgid "Predefined Exports"
+msgstr "Predefiniowane eksporty"
+
+msgid "Selected Records"
+msgstr "Wybrane rekordy"
+
+msgid "Listed Records"
+msgstr "Wymienione rekordy"
+
+msgid "Export:"
+msgstr "Eksport:"
+
+msgid "Ignore search limit"
+msgstr "Ignoruj limit wyszukiwań"
+
+msgid "Use locale format"
+msgstr "Użyj formatu lokalnego"
+
+msgid "Add Field Names"
+msgstr "Dodaj nazwy pól"
+
+msgid "%1 (string)"
+msgstr "%1 (string)"
+
+msgid "%1 (model name)"
+msgstr "%1 (model name)"
+
+msgid "%1 (record name)"
+msgstr "%1 (record name)"
+
+msgid "What is the name of this export?"
+msgstr "Jaka jest nazwa tego eksportu?"
+
+msgid "Override %1 definition?"
+msgstr "Czy nadpisać definicję %1?"
+
+msgid "E-mail %1"
+msgstr "E-mail %1"
+
+msgid "To:"
+msgstr "Do:"
+
+msgid "Cc:"
+msgstr "DW:"
+
+msgid "Bcc:"
+msgstr "UDW:"
+
+msgid "Subject:"
+msgstr "Temat:"
+
+msgid "Reports"
+msgstr "Raporty"
+
+msgid "Attachments"
+msgstr "Załączniki"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr "Wyślij"
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Usuń"
+
+msgid "Wizard"
+msgstr "Kreator"
+
+msgid "Translate view"
+msgstr "Widok tłumaczenia"
diff --git a/sao/locale/pt.po b/sao/locale/pt.po
new file mode 100644
index 0000000000..cc2fdafe97
--- /dev/null
+++ b/sao/locale/pt.po
@@ -0,0 +1,694 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 15 2019 16:53:42 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Apr 01 2019 13:14:07 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Mar 01 2019 00:18:48 GMT+0100 (CET)
+# Generated by grunt-xgettext on Fri Feb 01 2019 00:07:47 GMT+0100 (CET)
+# Generated by grunt-xgettext on Tue Jan 01 2019 00:13:07 GMT+0100 (CET)
+# Generated by grunt-xgettext on Sat Dec 01 2018 00:17:31 GMT+0100 (CET)
+# Generated by grunt-xgettext on Thu Nov 01 2018 00:08:43 GMT+0100 (CET)
+# Generated by grunt-xgettext on Mon Oct 01 2018 00:07:22 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Sep 14 2018 22:59:37 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sun Sep 09 2018 10:24:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Tue Sep 04 2018 13:54:29 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Sat Apr 14 2018 17:56:54 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Apr 06 2018 22:57:49 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Mon Oct 09 2017 19:35:04 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "Tem certeza que quer sair?"
+
+msgid "Favorites"
+msgstr "Favoritos"
+
+msgid "Manage..."
+msgstr "Gerenciar..."
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "Sair"
+
+msgid "Action"
+msgstr "Ação"
+
+msgid "New"
+msgstr "Novo"
+
+msgid "Save"
+msgstr "Gravar"
+
+msgid "Switch"
+msgstr "Alternar"
+
+msgid "Reload/Undo"
+msgstr "Recarregar/Desfazer"
+
+msgid "Duplicate"
+msgstr "Duplicar"
+
+msgid "Delete"
+msgstr "Apagar"
+
+msgid "Previous"
+msgstr "Anterior"
+
+msgid "Next"
+msgstr "Próximo"
+
+msgid "Search"
+msgstr "Buscar"
+
+msgid "Close Tab"
+msgstr "Fechar aba"
+
+msgid "Attachment"
+msgstr "Anexo"
+
+msgid "Note"
+msgstr "Nota"
+
+msgid "Relate"
+msgstr "Relacionar"
+
+msgid "Print"
+msgstr "Imprimir"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr "Aba anterior"
+
+msgid "Next tab"
+msgstr "Próxima aba"
+
+msgid "Global search"
+msgstr "Pesquisa global"
+
+msgid "Show this help"
+msgstr "Mostra esta ajuda"
+
+msgid "Keyboard shortcuts"
+msgstr "Teclas de atalho"
+
+msgid "Close"
+msgstr "Fechar"
+
+msgid "Global shortcuts"
+msgstr "Atalhos Globais"
+
+msgid "Tab shortcuts"
+msgstr "Atalhos para abas"
+
+msgid "Unable to reach the server."
+msgstr "Não foi possível se conectar ao servidor."
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "Entrar"
+
+msgid "User name"
+msgstr "Usuário"
+
+msgid "Database"
+msgstr "Banco de dados"
+
+msgid "Your selection:"
+msgstr "Sua seleção:"
+
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "A"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "s"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Por: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "a"
+
+msgid "Yes"
+msgstr "Sim"
+
+msgid "True"
+msgstr "Verdadeiro"
+
+msgid "t"
+msgstr "v"
+
+msgid "False"
+msgstr "Falso"
+
+msgid "Message: "
+msgstr "Mensagem: "
+
+msgid "Warning: "
+msgstr "Alerta: "
+
+msgid "Always ignore this warning."
+msgstr "Sempre ignorar este alerta."
+
+msgid "Do you want to proceed?"
+msgstr "Você deseja continuar?"
+
+msgid "No"
+msgstr "Não"
+
+msgid "Confirmation: "
+msgstr "Confirmação: "
+
+msgid "Concurrency Exception"
+msgstr "Exceção de concorrência"
+
+msgid "Write Concurrency Warning: "
+msgstr "Alerta de edição concorrente: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Este registro foi modificado enquanto você o editava."
+
+msgid "Choose:"
+msgstr "Escolha:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Cancelar\" para cancelar a gravação;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Comparar\" para ver a versão modificada;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Forçar Escrita\" para gravar sua versão atual."
+
+msgid "Compare"
+msgstr "\"Escrever mesmo assim\" para gravar sua versão atual."
+
+#, fuzzy
+msgid "Compare: %1"
+msgstr "\"Escrever mesmo assim\" para gravar sua versão atual."
+
+msgid "Write Anyway"
+msgstr "Forçar Escrita"
+
+msgid "Application Error"
+msgstr "Erro no Sistema"
+
+msgid "Report Bug"
+msgstr "Relatar erro"
+
+#, fuzzy
+msgid "Processing"
+msgstr "Processando..."
+
+msgid "Search..."
+msgstr "Buscar..."
+
+msgid "Create..."
+msgstr "Criar..."
+
+msgid "Download"
+msgstr "Baixar"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Fonte"
+
+msgid "Size"
+msgstr "Tamanho"
+
+msgid "Switch view"
+msgstr "Alternar a visão"
+
+msgid "Previous Record"
+msgstr "Registro anterior"
+
+msgid "Next Record"
+msgstr "Próximo registro"
+
+msgid "Create a new record"
+msgstr "Criar um novo registro"
+
+msgid "Save this record"
+msgstr "Gravar este registro"
+
+msgid "Reload"
+msgstr "Recarregar"
+
+msgid "View Logs..."
+msgstr "Ver logs..."
+
+msgid "Show revisions..."
+msgstr "Mostrar revisões..."
+
+msgid "Add an attachment to the record"
+msgstr "Adicionar um anexo ao registro"
+
+msgid "Add a note to the record"
+msgstr "Adicionar uma nota ao registro"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "Adicionar uma nota ao registro"
+
+msgid "Export"
+msgstr "Exportar"
+
+msgid "Import"
+msgstr "Importar"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"A seguinte ação requer que todas as abas sejam fechadas.\n"
+"Você deseja continuar?"
+
+msgid "Launch action"
+msgstr "Iniciar ação"
+
+msgid "Open related records"
+msgstr "Abrir registos relacionados"
+
+msgid "Print report"
+msgstr "Imprimir relatório"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Este registo foi modificado\n"
+"você deseja gravá-lo?"
+
+msgid "Record saved."
+msgstr "Registo gravado."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Trabalhando agora no(s) registo(s) duplicado(s)"
+
+msgid "Are you sure to remove this record?"
+msgstr "Você tem certeza que deseja apagar este registro?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Você tem certeza que deseja apagar estes registros?"
+
+msgid "Records removed."
+msgstr "Registos apagados."
+
+msgid "Records not removed."
+msgstr "Registros não apagados."
+
+msgid "You have to select one record."
+msgstr "Você precisa selecionar um registo."
+
+msgid "ID:"
+msgstr "ID:"
+
+#, fuzzy
+msgid "Created by:"
+msgstr "Criar..."
+
+#, fuzzy
+msgid "Created at:"
+msgstr "Data de criação"
+
+#, fuzzy
+msgid "Edited by:"
+msgstr "Criar..."
+
+#, fuzzy
+msgid "Edited at:"
+msgstr "Data de modificação"
+
+msgid "Model: "
+msgstr "Modelo: "
+
+msgid "Add..."
+msgstr "Adicionar..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "Anterior"
+
+#, fuzzy
+msgid "Template"
+msgstr "Relacionar"
+
+#, fuzzy
+msgid "Attachment (%1)"
+msgstr "Anexo (%1)"
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr " de "
+
+msgid "Filters"
+msgstr "Filtros"
+
+msgid "Clear Search"
+msgstr "Limpar Busca"
+
+msgid "Bookmarks"
+msgstr "Favoritos"
+
+msgid "Remove this bookmark"
+msgstr "Remover este favorito"
+
+msgid "Bookmark this filter"
+msgstr "Adicionar este filtro aos favoritos"
+
+msgid "Bookmark Name:"
+msgstr "Nome do favorito:"
+
+msgid "Show active records"
+msgstr "Mostrar registros ativos"
+
+msgid "Show inactive records"
+msgstr "Mostrar registros inativos"
+
+msgid "Find"
+msgstr "Localizar"
+
+msgid "From"
+msgstr "De"
+
+msgid "To"
+msgstr "Para"
+
+msgid "Open the calendar"
+msgstr "Abrir o calendário"
+
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgid "Created by"
+msgstr "Criar..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "Data de criação"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "Data de modificação"
+
+#, fuzzy
+msgid "\"%1\" is required."
+msgstr "\"%1\" é obrigatório"
+
+#, fuzzy
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" não é válido de acordo com seu domínio"
+
+#, fuzzy
+msgid "The values of \"%1\" are not valid."
+msgstr "Os valores para \"%1\" não são válidos"
+
+msgid "Pre-validation"
+msgstr "Pré-validação"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Traduzir"
+
+msgid "Edit"
+msgstr "Editar"
+
+msgid "Fuzzy"
+msgstr "Vago"
+
+msgid "You need to save the record before adding translations."
+msgstr "Você precisa gravar o registro antes de adicionar traduções."
+
+msgid "No other language available."
+msgstr "Não há outro idioma disponível."
+
+msgid "Show"
+msgstr "Mostrar"
+
+msgid "Hide"
+msgstr "Ocultar"
+
+#, fuzzy
+msgid "Open the record"
+msgstr "Abrir o registro "
+
+msgid "Clear the field"
+msgstr ""
+
+#, fuzzy
+msgid "Search a record"
+msgstr "Buscar um registro "
+
+msgid "Add"
+msgstr "Adicionar"
+
+msgid "Remove"
+msgstr "Remover"
+
+msgid "Open"
+msgstr "Abrir"
+
+msgid "Undelete"
+msgstr "Recuperar"
+
+msgid "Save As"
+msgstr "Salvar como"
+
+msgid "Save As..."
+msgstr "Salvar como..."
+
+msgid "Select"
+msgstr "Selecionar"
+
+msgid "Select..."
+msgstr "Selecionar..."
+
+msgid "Clear"
+msgstr "Limpar"
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Mais"
+
+msgid "Today"
+msgstr "Hoje"
+
+msgid "Month"
+msgstr "Mês"
+
+msgid "Week"
+msgstr "Semana"
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Selecione sua ação"
+
+msgid "No action defined."
+msgstr "Nenhuma ação definida."
+
+msgid "Attachments (%1)"
+msgstr "Anexo (%1)"
+
+msgid "Notes (%1)"
+msgstr "Notas (%1)"
+
+msgid "Search %1"
+msgstr "Buscar %1"
+
+msgid "Revision"
+msgstr "Revisão"
+
+msgid "All Fields"
+msgstr "Todos os Campos"
+
+msgid "Fields Selected"
+msgstr "Campos Selecionados"
+
+msgid "CSV Parameters"
+msgstr "Parâmetros CSV"
+
+msgid "Delimiter:"
+msgstr "Separador:"
+
+msgid "Quote Char:"
+msgstr "Caractere de Citação:"
+
+msgid "CSV Import: %1"
+msgstr "Importar CSV: %1"
+
+msgid "Auto-Detect"
+msgstr "Auto-Detectar"
+
+msgid "File to Import"
+msgstr "Arquivo a importar"
+
+msgid "Encoding:"
+msgstr "Codificação:"
+
+msgid "Lines to Skip:"
+msgstr "Linhas a Ignorar:"
+
+#, fuzzy
+msgid "You must select an import file first."
+msgstr "Primeiro você deve selecionar um arquivo para importar."
+
+#, fuzzy
+msgid "An error occured in loading the file."
+msgstr "Ocorreu um erro ao carregar o arquivo"
+
+msgid "Error processing the file at field %1."
+msgstr "Erro ao processar o arquivo no campo %1."
+
+msgid "Error"
+msgstr "Erro"
+
+msgid "Error occured in loading the file"
+msgstr "Ocorreu um erro ao carregar o arquivo"
+
+msgid "CSV Export: %1"
+msgstr "Exportar CSV: %1"
+
+msgid "Save Export"
+msgstr "Gravar Exportação"
+
+#, fuzzy
+msgid "URL Export"
+msgstr "Exportar"
+
+msgid "Delete Export"
+msgstr "Apagar Exportação"
+
+msgid "Predefined Exports"
+msgstr "Exportações Pre-definidas"
+
+#, fuzzy
+msgid "Selected Records"
+msgstr "Próximo registro"
+
+#, fuzzy
+msgid "Listed Records"
+msgstr "Próximo registro"
+
+#, fuzzy
+msgid "Export:"
+msgstr "Exportar"
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr "Adicionar Campos"
+
+msgid "%1 (string)"
+msgstr "%1 (texto)"
+
+msgid "%1 (model name)"
+msgstr "%1 (nome do modelo)"
+
+msgid "%1 (record name)"
+msgstr "%1 (nome do modelo)"
+
+msgid "What is the name of this export?"
+msgstr "Qual é o nome desta Exportação?"
+
+msgid "Override %1 definition?"
+msgstr "Sobrescrever definição %1?"
+
+msgid "E-mail %1"
+msgstr ""
+
+#, fuzzy
+msgid "To:"
+msgstr "Para"
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "Relatar erro"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "Anexo"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Remover"
+
+msgid "Wizard"
+msgstr "Assistente"
+
+msgid "Translate view"
+msgstr "Traduzir tela"
diff --git a/sao/locale/ro.po b/sao/locale/ro.po
new file mode 100644
index 0000000000..aada5833e5
--- /dev/null
+++ b/sao/locale/ro.po
@@ -0,0 +1,668 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Sun Mar 01 2020 02:18:27 GMT+0100 (Central
+# European Standard Time)
+msgid "Are your sure to leave?"
+msgstr "Sigur iesiti?"
+
+msgid "Favorites"
+msgstr "Favorite"
+
+msgid "Manage..."
+msgstr "Gestionare..."
+
+msgid "Logout"
+msgstr "Deconectare"
+
+msgid "Action"
+msgstr "Acțiune"
+
+msgid "New"
+msgstr "Nou"
+
+msgid "Save"
+msgstr "Salvare"
+
+msgid "Switch"
+msgstr "Schimba"
+
+msgid "Reload/Undo"
+msgstr "Reincarcare/Desfacere"
+
+msgid "Duplicate"
+msgstr "Duplicat"
+
+msgid "Delete"
+msgstr "Ștergere"
+
+msgid "Previous"
+msgstr "Anterior"
+
+msgid "Next"
+msgstr "Următor"
+
+msgid "Search"
+msgstr "Căutare"
+
+msgid "Close Tab"
+msgstr "Închidere fila"
+
+msgid "Attachment"
+msgstr "Atașament"
+
+msgid "Note"
+msgstr "Nota"
+
+msgid "Relate"
+msgstr "Relata"
+
+msgid "Print"
+msgstr "Tiparire"
+
+msgid "E-Mail"
+msgstr "E-Mail"
+
+msgid "Previous tab"
+msgstr "Fila anterioară"
+
+msgid "Next tab"
+msgstr "Fila următoare"
+
+msgid "Global search"
+msgstr "Căutare globală"
+
+msgid "Show this help"
+msgstr "Arătați acest ajutor"
+
+msgid "Keyboard shortcuts"
+msgstr "Comenzi rapide"
+
+msgid "Close"
+msgstr "Închidere"
+
+msgid "Global shortcuts"
+msgstr "Comenzile rapide globale"
+
+msgid "Tab shortcuts"
+msgstr "Comenzile rapide pentru file"
+
+msgid "Unable to reach the server."
+msgstr "Serverul nu se poate accessa."
+
+msgid "Too many requests. Try again later."
+msgstr "Prea multe cereri. Încercați mai târziu."
+
+msgid "Login"
+msgstr "Autentificare"
+
+msgid "User name"
+msgstr "Utilizator"
+
+msgid "Database"
+msgstr "Bază de date"
+
+msgid "Your selection:"
+msgstr "Selecția dvs.:"
+
+msgid "Cancel"
+msgstr "Anulare"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Y"
+msgstr "Y"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "w"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "De: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "y"
+
+msgid "Yes"
+msgstr "Da"
+
+msgid "True"
+msgstr "Adevărat"
+
+msgid "t"
+msgstr "t"
+
+msgid "False"
+msgstr "Fals"
+
+msgid "Message: "
+msgstr "Mesaj: "
+
+msgid "Warning: "
+msgstr "Avertizare: "
+
+msgid "Always ignore this warning."
+msgstr "Ignorați întotdeauna acest avertisment."
+
+msgid "Do you want to proceed?"
+msgstr "Doriți să continuați?"
+
+msgid "No"
+msgstr "Nu"
+
+msgid "Confirmation: "
+msgstr "Confirmare: "
+
+msgid "Concurrency Exception"
+msgstr "Excepție de concurgență"
+
+msgid "Write Concurrency Warning: "
+msgstr "Avertizare de scriere simultană: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Această înregistrare a fost modificată în timp ce o editați."
+
+msgid "Choose:"
+msgstr "Alegere:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "„Anulare” pentru a anula salvarea;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Comparație\" pentru a vedea versiunea modificată;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Scrie Oricum\" pentru a salva versiunea curentă."
+
+msgid "Compare"
+msgstr "Comparaţie"
+
+msgid "Compare: %1"
+msgstr "Comparație:% 1"
+
+msgid "Write Anyway"
+msgstr "Scrie oricum"
+
+msgid "Application Error"
+msgstr "Eroare de aplicație"
+
+msgid "Report Bug"
+msgstr "Raporteati eroare"
+
+msgid "Processing"
+msgstr "Se prelucreaza"
+
+msgid "Search..."
+msgstr "Căutare..."
+
+msgid "Create..."
+msgstr "Creare..."
+
+msgid "Download"
+msgstr "Descarca"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Font"
+
+msgid "Size"
+msgstr "Mărime"
+
+msgid "Switch view"
+msgstr "Comutare vedere"
+
+msgid "Previous Record"
+msgstr "Înregistrare anterioară"
+
+msgid "Next Record"
+msgstr "Următoarea înregistrare"
+
+msgid "Create a new record"
+msgstr "Creați o înregistrare nouă"
+
+msgid "Save this record"
+msgstr "Salvare înregistrare"
+
+msgid "Reload"
+msgstr "Reîncare"
+
+msgid "View Logs..."
+msgstr "Vizualizare jurnalele ..."
+
+msgid "Show revisions..."
+msgstr "Afișați revizii..."
+
+msgid "Add an attachment to the record"
+msgstr "Adăugați un atașament la înregistrare"
+
+msgid "Add a note to the record"
+msgstr "Adăugați o notă la înregistrare"
+
+msgid "E-Mail..."
+msgstr "E-Mail..."
+
+msgid "Send an e-mail using the record"
+msgstr "Trimiteți un e-mail folosind înregistrarea"
+
+msgid "Export"
+msgstr "Export"
+
+msgid "Import"
+msgstr "Import"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Următoarea acțiune necesită închiderea tuturor filelor.\n"
+"Doriți să continuați?"
+
+msgid "Launch action"
+msgstr "Lansare acțiune"
+
+msgid "Open related records"
+msgstr "Deschidere înregistrări conexe"
+
+msgid "Print report"
+msgstr "Tipărire raport"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Această înregistrare a fost modificată\n"
+"vrei sa o salvezi?"
+
+msgid "Record saved."
+msgstr "Înregistrare salvată."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Lucrează acum la înregistrările duplicate."
+
+msgid "Are you sure to remove this record?"
+msgstr "Sigur eliminați această înregistrare?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Sigur eliminați această înregistrare?"
+
+msgid "Records removed."
+msgstr "Înregistrările au fost eliminate."
+
+msgid "Records not removed."
+msgstr "Înregistrările nu au fost eliminate."
+
+msgid "You have to select one record."
+msgstr "Trebuie selectaa o singura înregistrare."
+
+msgid "ID:"
+msgstr "ID:"
+
+msgid "Created by:"
+msgstr "Creat de:"
+
+msgid "Created at:"
+msgstr "Creat la:"
+
+msgid "Edited by:"
+msgstr "Editat de:"
+
+msgid "Edited at:"
+msgstr "Editat la:"
+
+msgid "Model: "
+msgstr "Model: "
+
+msgid "Add..."
+msgstr "Adăuga..."
+
+msgid "Preview"
+msgstr "Previzualizare"
+
+msgid "Template"
+msgstr "Șablon"
+
+msgid "Attachment (%1)"
+msgstr "Atachment (%1)"
+
+msgid "Note (%1/%2)"
+msgstr "Notă (%1/%2)"
+
+msgid " of "
+msgstr " de "
+
+msgid "Filters"
+msgstr "Filtre"
+
+msgid "Clear Search"
+msgstr "Ștergeți Căutarea"
+
+msgid "Bookmarks"
+msgstr "Marcaje"
+
+msgid "Remove this bookmark"
+msgstr "Eliminare marcaj"
+
+msgid "Bookmark this filter"
+msgstr "Marcare filtru"
+
+msgid "Bookmark Name:"
+msgstr "Denumire marcaj:"
+
+msgid "Show active records"
+msgstr "Afișează înregistrări active"
+
+msgid "Show inactive records"
+msgstr "Afișează înregistrări active"
+
+msgid "Find"
+msgstr "Căutare"
+
+msgid "From"
+msgstr "De la"
+
+msgid "To"
+msgstr "La"
+
+msgid "Open the calendar"
+msgstr "Deschidere calendar"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "Created by"
+msgstr "Creat de"
+
+msgid "Created at"
+msgstr "Creat la"
+
+msgid "Modified by"
+msgstr "Modificat de"
+
+msgid "Modified at"
+msgstr "Modificat la"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" este necesar."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "\"%1\" nu este valabil dupa domeniul sau."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "Valorile lui \"%s\" nu sunt valide."
+
+msgid "Pre-validation"
+msgstr "Pre-validare"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Traducere"
+
+msgid "Edit"
+msgstr "Editare"
+
+msgid "Fuzzy"
+msgstr "Cautare aproximativa"
+
+msgid "You need to save the record before adding translations."
+msgstr "Trebuie să salvați înregistrarea înainte de a adăuga traduceri."
+
+msgid "No other language available."
+msgstr "Nu există altă limbă disponibilă."
+
+msgid "Show"
+msgstr "Afișare"
+
+msgid "Hide"
+msgstr "Ascunde"
+
+msgid "Open the record"
+msgstr "Deschidere inregistrare"
+
+msgid "Clear the field"
+msgstr "Golire câmp"
+
+msgid "Search a record"
+msgstr "Cautare in inregistrare"
+
+msgid "Add"
+msgstr "Adăuga"
+
+msgid "Remove"
+msgstr "Eliminare"
+
+msgid "Open"
+msgstr "Deschidere"
+
+msgid "Undelete"
+msgstr "Anulați ștergerea"
+
+msgid "Save As"
+msgstr "Salvează ca"
+
+msgid "Save As..."
+msgstr "Salvare că..."
+
+msgid "Select"
+msgstr "Selectați"
+
+msgid "Select..."
+msgstr "Selectare..."
+
+msgid "Clear"
+msgstr "Golire"
+
+msgid "Choose a language"
+msgstr "Alegere limbă"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Mai multe"
+
+msgid "Today"
+msgstr "Astăzi"
+
+msgid "Month"
+msgstr "Lună"
+
+msgid "Week"
+msgstr "Săptămână"
+
+msgid "Day"
+msgstr "Zi"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Selectați acțiunea"
+
+msgid "No action defined."
+msgstr "Nicio acțiune definită."
+
+msgid "Attachments (%1)"
+msgstr "Atașamente (%1)"
+
+msgid "Notes (%1)"
+msgstr "Note (%1)"
+
+msgid "Search %1"
+msgstr "Căutare %1"
+
+msgid "Revision"
+msgstr "Revizuire"
+
+msgid "All Fields"
+msgstr "Toate câmpurile"
+
+msgid "Fields Selected"
+msgstr "Câmpuri selectate"
+
+msgid "CSV Parameters"
+msgstr "Parametri CSV"
+
+msgid "Delimiter:"
+msgstr "Delimitator:"
+
+msgid "Quote Char:"
+msgstr "Caracter de cotatie:"
+
+msgid "CSV Import: %1"
+msgstr "Import CSV: %1"
+
+msgid "Auto-Detect"
+msgstr "Auto-detectare"
+
+msgid "File to Import"
+msgstr "Fișier de importat"
+
+msgid "Encoding:"
+msgstr "Codifiere:"
+
+msgid "Lines to Skip:"
+msgstr "Rânduri de sarit:"
+
+msgid "You must select an import file first."
+msgstr "Mai întâi trebuie să selectați un fișier de import."
+
+msgid "An error occured in loading the file."
+msgstr "A apărut o eroare la încărcarea fișierului."
+
+msgid "Error processing the file at field %1."
+msgstr "Eroare la procesarea fișierului la câmpul %1."
+
+msgid "Error"
+msgstr "Eroare"
+
+msgid "Error occured in loading the file"
+msgstr "A apărut o eroare la încărcarea fișierului"
+
+msgid "CSV Export: %1"
+msgstr "Export CSV: %1"
+
+msgid "Save Export"
+msgstr "Salvare Export"
+
+msgid "URL Export"
+msgstr "Export URL"
+
+msgid "Delete Export"
+msgstr "Ștergere Export"
+
+msgid "Predefined Exports"
+msgstr "Exporturi predefinite"
+
+msgid "Selected Records"
+msgstr "Înregistrări selectate"
+
+msgid "Listed Records"
+msgstr "Înregistrări enumerate"
+
+msgid "Export:"
+msgstr "Export:"
+
+msgid "Ignore search limit"
+msgstr "Ignorați limita de căutare"
+
+msgid "Use locale format"
+msgstr "Utilizați formatul local"
+
+msgid "Add Field Names"
+msgstr "Adăugare denumire de câmp"
+
+msgid "%1 (string)"
+msgstr "%1 (șir)"
+
+msgid "%1 (model name)"
+msgstr "%1 (denumire model)"
+
+msgid "%1 (record name)"
+msgstr "%1 (denmire inregistrare)"
+
+msgid "What is the name of this export?"
+msgstr "Care este numele acestui export?"
+
+#, fuzzy
+msgid "Override %1 definition?"
+msgstr "Trece peste definiția %1?"
+
+msgid "E-mail %1"
+msgstr "E-mail %1"
+
+msgid "To:"
+msgstr "Destinatar:"
+
+msgid "Cc:"
+msgstr "CC:"
+
+msgid "Bcc:"
+msgstr "Bcc:"
+
+msgid "Subject:"
+msgstr "Subiect:"
+
+msgid "Reports"
+msgstr "Raport"
+
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-11-16 13:23+0000\n"
+"PO-Revision-Date: 2020-11-23 09:53+0000\n"
+"Last-Translator: Dimitrios Moustos \n"
+"Language-Team: LANGUAGE \n"
+"Language: ro\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;\n"
+"X-Generator: Weblate 4.3.2\n"
+
+msgid "Attachments"
+msgstr "Atașamente"
+
+msgid "Files"
+msgstr "Fişiere"
+
+msgid "Send"
+msgstr "Trimite"
+
+msgid "Remove File"
+msgstr "Eliminare Fişier"
+
+msgid "Wizard"
+msgstr "Asistent"
+
+msgid "Translate view"
+msgstr "Traducere vedere"
diff --git a/sao/locale/ru.po b/sao/locale/ru.po
new file mode 100644
index 0000000000..c80c418437
--- /dev/null
+++ b/sao/locale/ru.po
@@ -0,0 +1,656 @@
+#
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+#, fuzzy
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+#, fuzzy
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr ""
+
+msgid "Created at:"
+msgstr ""
+
+msgid "Edited by:"
+msgstr ""
+
+msgid "Edited at:"
+msgstr ""
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr " из "
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr ""
+
+msgid "Created at"
+msgstr ""
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ""
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+#, fuzzy
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid ""
+msgstr ""
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-05-01 15:33+0000\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/sl.po b/sao/locale/sl.po
new file mode 100644
index 0000000000..4145a2e89e
--- /dev/null
+++ b/sao/locale/sl.po
@@ -0,0 +1,677 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr "Zaznamki"
+
+msgid "Manage..."
+msgstr ""
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "Odjava"
+
+msgid "Action"
+msgstr "Ukrep"
+
+msgid "New"
+msgstr "Novo"
+
+msgid "Save"
+msgstr "Shrani"
+
+msgid "Switch"
+msgstr "Preklopi"
+
+msgid "Reload/Undo"
+msgstr "Ponovno naloži"
+
+msgid "Duplicate"
+msgstr "Podvoji"
+
+msgid "Delete"
+msgstr "Zbriši"
+
+msgid "Previous"
+msgstr "Prejšnji"
+
+msgid "Next"
+msgstr "Naslednji"
+
+msgid "Search"
+msgstr "Išči"
+
+msgid "Close Tab"
+msgstr "Zapri zavihek"
+
+msgid "Attachment"
+msgstr "Priponka"
+
+msgid "Note"
+msgstr "Zabeležka"
+
+msgid "Relate"
+msgstr "Veza"
+
+msgid "Print"
+msgstr "Tisk"
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr "Zapri"
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr "Ni se mogoče povezati s strežnikom."
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr "Prijava"
+
+msgid "User name"
+msgstr "Uporabniško ime"
+
+msgid "Database"
+msgstr "Podatkovna baza"
+
+msgid "Your selection:"
+msgstr "Vaša izbira:"
+
+msgid "Cancel"
+msgstr "Prekliči"
+
+msgid "OK"
+msgstr "V redu"
+
+msgid "Y"
+msgstr "l"
+
+msgid "M"
+msgstr "M"
+
+msgid "w"
+msgstr "t"
+
+msgid "d"
+msgstr "d"
+
+msgid "h"
+msgstr "h"
+
+msgid "m"
+msgstr "m"
+
+msgid "s"
+msgstr "s"
+
+msgid "By: "
+msgstr "Od: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "d"
+
+msgid "Yes"
+msgstr "Da"
+
+msgid "True"
+msgstr "Da"
+
+msgid "t"
+msgstr "d"
+
+msgid "False"
+msgstr "Ne"
+
+msgid "Message: "
+msgstr "Sporočilo: "
+
+msgid "Warning: "
+msgstr "Opozorilo: "
+
+msgid "Always ignore this warning."
+msgstr "Vedno prezri to opozorilo."
+
+msgid "Do you want to proceed?"
+msgstr "Ali želiš nadaljevati?"
+
+msgid "No"
+msgstr "Ne"
+
+msgid "Confirmation: "
+msgstr "Potrditev: "
+
+msgid "Concurrency Exception"
+msgstr "Napaka sočasnega izvajanja"
+
+msgid "Write Concurrency Warning: "
+msgstr "Opozorilo sočasnega zapisovanja: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "Ta zapis je bil spremenjen v času vašega popravljanja."
+
+msgid "Choose:"
+msgstr "Izberi:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"Prekliči\" - prekliči spremembe;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"Primerjaj\" - primerjaj z drugo različico;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"Vseeno shrani\" - shrani trenutno različico."
+
+msgid "Compare"
+msgstr "Primerjaj"
+
+#, fuzzy
+msgid "Compare: %1"
+msgstr "Primerjaj"
+
+msgid "Write Anyway"
+msgstr "Vseeno shrani"
+
+msgid "Application Error"
+msgstr "Programska napaka"
+
+msgid "Report Bug"
+msgstr "Prijavi programsko napako"
+
+#, fuzzy
+msgid "Processing"
+msgstr "Obdelava ..."
+
+msgid "Search..."
+msgstr "Išči ..."
+
+msgid "Create..."
+msgstr "Izdelaj ..."
+
+msgid "Download"
+msgstr "Prenesi"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "Pisava"
+
+msgid "Size"
+msgstr "Velikost"
+
+msgid "Switch view"
+msgstr "Preklopi pogled"
+
+msgid "Previous Record"
+msgstr "Prejšnji zapis"
+
+msgid "Next Record"
+msgstr "Naslednji zapis"
+
+msgid "Create a new record"
+msgstr "Ustvari nov zapis"
+
+msgid "Save this record"
+msgstr "Shrani ta zapis"
+
+msgid "Reload"
+msgstr "Ponovno naloži"
+
+msgid "View Logs..."
+msgstr "Prikaži dnevnik ..."
+
+msgid "Show revisions..."
+msgstr "Prikaži revizije ..."
+
+msgid "Add an attachment to the record"
+msgstr "Dodaj priponko zapisu"
+
+msgid "Add a note to the record"
+msgstr "Dodaj zapisu zabeležko"
+
+msgid "E-Mail..."
+msgstr ""
+
+#, fuzzy
+msgid "Send an e-mail using the record"
+msgstr "Dodaj zapisu zabeležko"
+
+msgid "Export"
+msgstr "Izvoz"
+
+msgid "Import"
+msgstr "Uvoz"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"Za slednji ukrep se bodo zaprli vsi zavihki.\n"
+"Ali želiš nadaljevati?"
+
+msgid "Launch action"
+msgstr "Zaženi ukrep"
+
+msgid "Open related records"
+msgstr "Odpri povezane zapise"
+
+msgid "Print report"
+msgstr "Izpis poročila"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"Ta zapis je bil spremenjen,\n"
+"ga želiš shraniti?"
+
+msgid "Record saved."
+msgstr "Zapis shranjen."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "Trenutno se dela na podvojenem zapisu."
+
+msgid "Are you sure to remove this record?"
+msgstr "Ali res želiš zbrisati ta zapis?"
+
+msgid "Are you sure to remove those records?"
+msgstr "Ali res želiš zbrisati te zapise?"
+
+msgid "Records removed."
+msgstr "Zapisi zbrisani."
+
+msgid "Records not removed."
+msgstr "Zapisi niso bili zbrisani."
+
+msgid "You have to select one record."
+msgstr "En zapis mora biti izbran."
+
+msgid "ID:"
+msgstr "ID:"
+
+#, fuzzy
+msgid "Created by:"
+msgstr "Izdelaj ..."
+
+#, fuzzy
+msgid "Created at:"
+msgstr "Ustvarjeno"
+
+#, fuzzy
+msgid "Edited by:"
+msgstr "Izdelaj ..."
+
+#, fuzzy
+msgid "Edited at:"
+msgstr "Popravljeno"
+
+msgid "Model: "
+msgstr "Model: "
+
+msgid "Add..."
+msgstr ""
+
+#, fuzzy
+msgid "Preview"
+msgstr "Prejšnji"
+
+#, fuzzy
+msgid "Template"
+msgstr "Veza"
+
+#, fuzzy
+msgid "Attachment (%1)"
+msgstr "Priponke (%1)"
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr "Filtri"
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr "Zaznamki"
+
+msgid "Remove this bookmark"
+msgstr "Zbriši ta zaznamek"
+
+msgid "Bookmark this filter"
+msgstr "Shrani ta filter"
+
+msgid "Bookmark Name:"
+msgstr "Ime zaznamka:"
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr "Najdi"
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr "ID"
+
+#, fuzzy
+msgid "Created by"
+msgstr "Izdelaj ..."
+
+#, fuzzy
+msgid "Created at"
+msgstr "Ustvarjeno"
+
+msgid "Modified by"
+msgstr ""
+
+#, fuzzy
+msgid "Modified at"
+msgstr "Popravljeno"
+
+#, fuzzy
+msgid "\"%1\" is required."
+msgstr "Polje \"%1\" je obvezno"
+
+#, fuzzy
+msgid "\"%1\" is not valid according to its domain."
+msgstr "Polje \"%1\" ni veljavno glede na svojo domeno"
+
+#, fuzzy
+msgid "The values of \"%1\" are not valid."
+msgstr "Vrednosti polja \"%1\" niso veljavne"
+
+msgid "Pre-validation"
+msgstr "Predpreverjanje"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "Prevod"
+
+msgid "Edit"
+msgstr "Uredi"
+
+msgid "Fuzzy"
+msgstr "Nejasno"
+
+msgid "You need to save the record before adding translations."
+msgstr "Pred prevajanjem mora biti zapis shranjen."
+
+msgid "No other language available."
+msgstr "Drugega jezika ni na voljo."
+
+msgid "Show"
+msgstr "Pokaži"
+
+msgid "Hide"
+msgstr "Skrij"
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr "Dodaj"
+
+msgid "Remove"
+msgstr "Odstrani"
+
+msgid "Open"
+msgstr "Odpri"
+
+msgid "Undelete"
+msgstr "Povrni"
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr "Izberi"
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr "Počisti"
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "Več"
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "Izberi ukrep"
+
+msgid "No action defined."
+msgstr "Nobenega ukrepa še ni določenega."
+
+msgid "Attachments (%1)"
+msgstr "Priponke (%1)"
+
+msgid "Notes (%1)"
+msgstr "Zabeležke (%1)"
+
+msgid "Search %1"
+msgstr "Išči %1"
+
+msgid "Revision"
+msgstr "Različica"
+
+msgid "All Fields"
+msgstr "Vsa polja"
+
+msgid "Fields Selected"
+msgstr "Izbrana polja"
+
+msgid "CSV Parameters"
+msgstr "CSV parametri"
+
+msgid "Delimiter:"
+msgstr "Ločilo:"
+
+msgid "Quote Char:"
+msgstr "Narekovaj:"
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr "Samozaznava"
+
+msgid "File to Import"
+msgstr "Datoteka za uvoz"
+
+msgid "Encoding:"
+msgstr "Kodiranje:"
+
+msgid "Lines to Skip:"
+msgstr "Izpuščene vrstice:"
+
+#, fuzzy
+msgid "You must select an import file first."
+msgstr "Najprej je potrebno izbrati datoteko za uvoz"
+
+#, fuzzy
+msgid "An error occured in loading the file."
+msgstr "Pri nalaganju datoteke je prišlo do napake"
+
+msgid "Error processing the file at field %1."
+msgstr "Napaka pri obdelavi datoteke pri polju %1."
+
+msgid "Error"
+msgstr "Napaka"
+
+msgid "Error occured in loading the file"
+msgstr "Pri nalaganju datoteke je prišlo do napake"
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr "Shrani izvoz"
+
+#, fuzzy
+msgid "URL Export"
+msgstr "Izvoz"
+
+msgid "Delete Export"
+msgstr "Zbriši izvoz"
+
+msgid "Predefined Exports"
+msgstr "Prednastavljeni izvozi"
+
+#, fuzzy
+msgid "Selected Records"
+msgstr "Naslednji zapis"
+
+#, fuzzy
+msgid "Listed Records"
+msgstr "Naslednji zapis"
+
+#, fuzzy
+msgid "Export:"
+msgstr "Izvoz"
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr "Dodaj imena polj"
+
+msgid "%1 (string)"
+msgstr "%1 (niz)"
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr "%1 (ime zapisa)"
+
+msgid "What is the name of this export?"
+msgstr "Ime izvoza?"
+
+msgid "Override %1 definition?"
+msgstr "Prepis definicije %1?"
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+#, fuzzy
+msgid "Reports"
+msgstr "Prijavi programsko napako"
+
+#, fuzzy
+msgid "Attachments"
+msgstr "Priponka"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+#, fuzzy
+msgid "Remove File"
+msgstr "Odstrani"
+
+msgid "Wizard"
+msgstr "Čarovnik"
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/tr.po b/sao/locale/tr.po
new file mode 100644
index 0000000000..15ae48e353
--- /dev/null
+++ b/sao/locale/tr.po
@@ -0,0 +1,646 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr ""
+
+msgid "Favorites"
+msgstr ""
+
+msgid "Manage..."
+msgstr ""
+
+msgid "Logout"
+msgstr ""
+
+msgid "Action"
+msgstr ""
+
+msgid "New"
+msgstr ""
+
+msgid "Save"
+msgstr ""
+
+msgid "Switch"
+msgstr ""
+
+msgid "Reload/Undo"
+msgstr ""
+
+msgid "Duplicate"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Previous"
+msgstr ""
+
+msgid "Next"
+msgstr ""
+
+msgid "Search"
+msgstr ""
+
+msgid "Close Tab"
+msgstr ""
+
+msgid "Attachment"
+msgstr ""
+
+msgid "Note"
+msgstr ""
+
+msgid "Relate"
+msgstr ""
+
+msgid "Print"
+msgstr ""
+
+msgid "E-Mail"
+msgstr ""
+
+msgid "Previous tab"
+msgstr ""
+
+msgid "Next tab"
+msgstr ""
+
+msgid "Global search"
+msgstr ""
+
+msgid "Show this help"
+msgstr ""
+
+msgid "Keyboard shortcuts"
+msgstr ""
+
+msgid "Close"
+msgstr ""
+
+msgid "Global shortcuts"
+msgstr ""
+
+msgid "Tab shortcuts"
+msgstr ""
+
+msgid "Unable to reach the server."
+msgstr ""
+
+msgid "Too many requests. Try again later."
+msgstr ""
+
+msgid "Login"
+msgstr ""
+
+msgid "User name"
+msgstr ""
+
+msgid "Database"
+msgstr ""
+
+msgid "Your selection:"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "OK"
+msgstr ""
+
+msgid "Y"
+msgstr ""
+
+msgid "M"
+msgstr ""
+
+msgid "w"
+msgstr ""
+
+msgid "d"
+msgstr ""
+
+msgid "h"
+msgstr ""
+
+msgid "m"
+msgstr ""
+
+msgid "s"
+msgstr ""
+
+msgid "By: "
+msgstr ""
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr ""
+
+msgid "Yes"
+msgstr ""
+
+msgid "True"
+msgstr ""
+
+msgid "t"
+msgstr ""
+
+msgid "False"
+msgstr ""
+
+msgid "Message: "
+msgstr ""
+
+msgid "Warning: "
+msgstr ""
+
+msgid "Always ignore this warning."
+msgstr ""
+
+msgid "Do you want to proceed?"
+msgstr ""
+
+msgid "No"
+msgstr ""
+
+msgid "Confirmation: "
+msgstr ""
+
+msgid "Concurrency Exception"
+msgstr ""
+
+msgid "Write Concurrency Warning: "
+msgstr ""
+
+msgid "This record has been modified while you were editing it."
+msgstr ""
+
+msgid "Choose:"
+msgstr ""
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr ""
+
+msgid "\"Compare\" to see the modified version;"
+msgstr ""
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr ""
+
+msgid "Compare"
+msgstr ""
+
+msgid "Compare: %1"
+msgstr ""
+
+msgid "Write Anyway"
+msgstr ""
+
+msgid "Application Error"
+msgstr ""
+
+msgid "Report Bug"
+msgstr ""
+
+msgid "Processing"
+msgstr ""
+
+msgid "Search..."
+msgstr ""
+
+msgid "Create..."
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr ""
+
+msgid "Size"
+msgstr ""
+
+msgid "Switch view"
+msgstr ""
+
+msgid "Previous Record"
+msgstr ""
+
+msgid "Next Record"
+msgstr ""
+
+msgid "Create a new record"
+msgstr ""
+
+msgid "Save this record"
+msgstr ""
+
+msgid "Reload"
+msgstr ""
+
+msgid "View Logs..."
+msgstr ""
+
+msgid "Show revisions..."
+msgstr ""
+
+msgid "Add an attachment to the record"
+msgstr ""
+
+msgid "Add a note to the record"
+msgstr ""
+
+msgid "E-Mail..."
+msgstr ""
+
+msgid "Send an e-mail using the record"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Import"
+msgstr ""
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+
+msgid "Launch action"
+msgstr ""
+
+msgid "Open related records"
+msgstr ""
+
+msgid "Print report"
+msgstr ""
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+
+msgid "Record saved."
+msgstr ""
+
+msgid "Working now on the duplicated record(s)."
+msgstr ""
+
+msgid "Are you sure to remove this record?"
+msgstr ""
+
+msgid "Are you sure to remove those records?"
+msgstr ""
+
+msgid "Records removed."
+msgstr ""
+
+msgid "Records not removed."
+msgstr ""
+
+msgid "You have to select one record."
+msgstr ""
+
+msgid "ID:"
+msgstr ""
+
+msgid "Created by:"
+msgstr ""
+
+msgid "Created at:"
+msgstr ""
+
+msgid "Edited by:"
+msgstr ""
+
+msgid "Edited at:"
+msgstr ""
+
+msgid "Model: "
+msgstr ""
+
+msgid "Add..."
+msgstr ""
+
+msgid "Preview"
+msgstr ""
+
+msgid "Template"
+msgstr ""
+
+msgid "Attachment (%1)"
+msgstr ""
+
+msgid "Note (%1/%2)"
+msgstr ""
+
+msgid " of "
+msgstr ""
+
+msgid "Filters"
+msgstr ""
+
+msgid "Clear Search"
+msgstr ""
+
+msgid "Bookmarks"
+msgstr ""
+
+msgid "Remove this bookmark"
+msgstr ""
+
+msgid "Bookmark this filter"
+msgstr ""
+
+msgid "Bookmark Name:"
+msgstr ""
+
+msgid "Show active records"
+msgstr ""
+
+msgid "Show inactive records"
+msgstr ""
+
+msgid "Find"
+msgstr ""
+
+msgid "From"
+msgstr ""
+
+msgid "To"
+msgstr ""
+
+msgid "Open the calendar"
+msgstr ""
+
+msgid "ID"
+msgstr ""
+
+msgid "Created by"
+msgstr ""
+
+msgid "Created at"
+msgstr ""
+
+msgid "Modified by"
+msgstr ""
+
+msgid "Modified at"
+msgstr ""
+
+msgid "\"%1\" is required."
+msgstr ""
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr ""
+
+msgid "The values of \"%1\" are not valid."
+msgstr ""
+
+msgid "Pre-validation"
+msgstr ""
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Fuzzy"
+msgstr ""
+
+msgid "You need to save the record before adding translations."
+msgstr ""
+
+msgid "No other language available."
+msgstr ""
+
+msgid "Show"
+msgstr ""
+
+msgid "Hide"
+msgstr ""
+
+msgid "Open the record"
+msgstr ""
+
+msgid "Clear the field"
+msgstr ""
+
+msgid "Search a record"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Open"
+msgstr ""
+
+msgid "Undelete"
+msgstr ""
+
+msgid "Save As"
+msgstr ""
+
+msgid "Save As..."
+msgstr ""
+
+msgid "Select"
+msgstr ""
+
+msgid "Select..."
+msgstr ""
+
+msgid "Clear"
+msgstr ""
+
+msgid "Choose a language"
+msgstr ""
+
+msgid "%1%"
+msgstr ""
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr ""
+
+msgid "Today"
+msgstr ""
+
+msgid "Month"
+msgstr ""
+
+msgid "Week"
+msgstr ""
+
+msgid "Day"
+msgstr ""
+
+msgid ",..."
+msgstr ""
+
+msgid "%1 (%2)"
+msgstr ""
+
+msgid "Select your action"
+msgstr ""
+
+msgid "No action defined."
+msgstr ""
+
+msgid "Attachments (%1)"
+msgstr ""
+
+msgid "Notes (%1)"
+msgstr ""
+
+msgid "Search %1"
+msgstr ""
+
+msgid "Revision"
+msgstr ""
+
+msgid "All Fields"
+msgstr ""
+
+msgid "Fields Selected"
+msgstr ""
+
+msgid "CSV Parameters"
+msgstr ""
+
+msgid "Delimiter:"
+msgstr ""
+
+msgid "Quote Char:"
+msgstr ""
+
+msgid "CSV Import: %1"
+msgstr ""
+
+msgid "Auto-Detect"
+msgstr ""
+
+msgid "File to Import"
+msgstr ""
+
+msgid "Encoding:"
+msgstr ""
+
+msgid "Lines to Skip:"
+msgstr ""
+
+msgid "You must select an import file first."
+msgstr ""
+
+msgid "An error occured in loading the file."
+msgstr ""
+
+msgid "Error processing the file at field %1."
+msgstr ""
+
+msgid "Error"
+msgstr ""
+
+msgid "Error occured in loading the file"
+msgstr ""
+
+msgid "CSV Export: %1"
+msgstr ""
+
+msgid "Save Export"
+msgstr ""
+
+msgid "URL Export"
+msgstr ""
+
+msgid "Delete Export"
+msgstr ""
+
+msgid "Predefined Exports"
+msgstr ""
+
+msgid "Selected Records"
+msgstr ""
+
+msgid "Listed Records"
+msgstr ""
+
+msgid "Export:"
+msgstr ""
+
+msgid "Ignore search limit"
+msgstr ""
+
+msgid "Use locale format"
+msgstr ""
+
+msgid "Add Field Names"
+msgstr ""
+
+msgid "%1 (string)"
+msgstr ""
+
+msgid "%1 (model name)"
+msgstr ""
+
+msgid "%1 (record name)"
+msgstr ""
+
+msgid "What is the name of this export?"
+msgstr ""
+
+msgid "Override %1 definition?"
+msgstr ""
+
+msgid "E-mail %1"
+msgstr ""
+
+msgid "To:"
+msgstr ""
+
+msgid "Cc:"
+msgstr ""
+
+msgid "Bcc:"
+msgstr ""
+
+msgid "Subject:"
+msgstr ""
+
+msgid "Reports"
+msgstr ""
+
+msgid "Attachments"
+msgstr ""
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr ""
+
+msgid "Remove File"
+msgstr ""
+
+msgid "Wizard"
+msgstr ""
+
+msgid "Translate view"
+msgstr ""
diff --git a/sao/locale/zh_CN.po b/sao/locale/zh_CN.po
new file mode 100644
index 0000000000..4bc8ce293b
--- /dev/null
+++ b/sao/locale/zh_CN.po
@@ -0,0 +1,654 @@
+# Generated by grunt-xgettext on Wed May 01 2019 00:09:20 GMT+0200 (CEST)
+msgid ""
+msgstr "Content-Type: text/plain; charset=utf-8\n"
+
+msgid "Are your sure to leave?"
+msgstr "您确定要离开吗?"
+
+msgid "Favorites"
+msgstr "收藏(_V)"
+
+msgid "Manage..."
+msgstr "管理..."
+
+# Generated by grunt-xgettext on Thu Mar 31 2016 17:50:31 GMT+0200 (CEST)
+# Generated by grunt-xgettext on Fri Oct 16 2015 17:37:32 GMT+0200 (CEST)
+msgid "Logout"
+msgstr "注销"
+
+msgid "Action"
+msgstr "操作"
+
+msgid "New"
+msgstr "新建"
+
+msgid "Save"
+msgstr "保存"
+
+msgid "Switch"
+msgstr "切换"
+
+msgid "Reload/Undo"
+msgstr "刷新/撤销"
+
+msgid "Duplicate"
+msgstr "复制"
+
+msgid "Delete"
+msgstr "删除"
+
+msgid "Previous"
+msgstr "向前"
+
+msgid "Next"
+msgstr "向后"
+
+msgid "Search"
+msgstr "查找"
+
+msgid "Close Tab"
+msgstr "关闭标签"
+
+msgid "Attachment"
+msgstr "附件"
+
+msgid "Note"
+msgstr "备注"
+
+msgid "Relate"
+msgstr "关联"
+
+msgid "Print"
+msgstr "打印"
+
+msgid "E-Mail"
+msgstr "电子邮件"
+
+msgid "Previous tab"
+msgstr "上一标签(_P)"
+
+msgid "Next tab"
+msgstr "下一标签(_N)"
+
+msgid "Global search"
+msgstr "全局查找"
+
+msgid "Show this help"
+msgstr "显示此帮助"
+
+msgid "Keyboard shortcuts"
+msgstr "键盘快捷键"
+
+msgid "Close"
+msgstr "关闭"
+
+msgid "Global shortcuts"
+msgstr "全局快捷方式"
+
+msgid "Tab shortcuts"
+msgstr "标签快捷方式"
+
+msgid "Unable to reach the server."
+msgstr "无法连接服务器."
+
+msgid "Too many requests. Try again later."
+msgstr "请求太多,请稍后重试."
+
+msgid "Login"
+msgstr "登录"
+
+msgid "User name"
+msgstr "用户名"
+
+msgid "Database"
+msgstr "数据库"
+
+msgid "Your selection:"
+msgstr "已选择:"
+
+msgid "Cancel"
+msgstr "取消"
+
+msgid "OK"
+msgstr "确定"
+
+msgid "Y"
+msgstr "年"
+
+msgid "M"
+msgstr "月"
+
+msgid "w"
+msgstr "星期"
+
+msgid "d"
+msgstr "日"
+
+msgid "h"
+msgstr "时"
+
+msgid "m"
+msgstr "分"
+
+msgid "s"
+msgstr "秒"
+
+msgid "By: "
+msgstr "By: "
+
+msgid ", "
+msgstr ", "
+
+msgid "y"
+msgstr "y"
+
+msgid "Yes"
+msgstr "是"
+
+msgid "True"
+msgstr "True"
+
+msgid "t"
+msgstr "t"
+
+msgid "False"
+msgstr "False"
+
+msgid "Message: "
+msgstr "信息: "
+
+msgid "Warning: "
+msgstr "警告: "
+
+msgid "Always ignore this warning."
+msgstr "下次不再提示."
+
+msgid "Do you want to proceed?"
+msgstr "继续此项操作 ?"
+
+msgid "No"
+msgstr "否"
+
+msgid "Confirmation: "
+msgstr "确认: "
+
+msgid "Concurrency Exception"
+msgstr "并行操作异常"
+
+msgid "Write Concurrency Warning: "
+msgstr "同步操作警告: "
+
+msgid "This record has been modified while you were editing it."
+msgstr "在你编辑的时候,这条记录已经被更改."
+
+msgid "Choose:"
+msgstr "选择:"
+
+msgid "\"Cancel\" to cancel saving;"
+msgstr "\"取消\" 不保存;"
+
+msgid "\"Compare\" to see the modified version;"
+msgstr "\"对比\" 查看变更的数据;"
+
+msgid "\"Write Anyway\" to save your current version."
+msgstr "\"覆写\" 保存当前数据."
+
+msgid "Compare"
+msgstr "比较"
+
+msgid "Compare: %1"
+msgstr "比较: %1"
+
+msgid "Write Anyway"
+msgstr "覆写"
+
+msgid "Application Error"
+msgstr "程序错误"
+
+msgid "Report Bug"
+msgstr "报告Bug"
+
+msgid "Processing"
+msgstr "正在处理"
+
+msgid "Search..."
+msgstr "查找..."
+
+msgid "Create..."
+msgstr "新建..."
+
+msgid "Download"
+msgstr "下载"
+
+msgid "..."
+msgstr "..."
+
+msgid "Font"
+msgstr "字体"
+
+msgid "Size"
+msgstr "大小"
+
+msgid "Switch view"
+msgstr "切换视图"
+
+msgid "Previous Record"
+msgstr "前一条记录"
+
+msgid "Next Record"
+msgstr "后一条记录"
+
+msgid "Create a new record"
+msgstr "新建记录"
+
+msgid "Save this record"
+msgstr "保存当前记录"
+
+msgid "Reload"
+msgstr "重载"
+
+msgid "View Logs..."
+msgstr "查看日志..."
+
+msgid "Show revisions..."
+msgstr "显示修订版本..."
+
+msgid "Add an attachment to the record"
+msgstr "为当前记录添加附件"
+
+msgid "Add a note to the record"
+msgstr "为当前记录添加注释"
+
+msgid "E-Mail..."
+msgstr "用电子邮件发送..."
+
+msgid "Send an e-mail using the record"
+msgstr "使用当前记录发送电子邮件"
+
+msgid "Export"
+msgstr "导出"
+
+msgid "Import"
+msgstr "导入"
+
+msgid ""
+"The following action requires to close all tabs.\n"
+"Do you want to continue?"
+msgstr ""
+"该操作需要关闭所有标签.\n"
+"是否继续?"
+
+msgid "Launch action"
+msgstr "执行操作"
+
+msgid "Open related records"
+msgstr "打开关联记录"
+
+msgid "Print report"
+msgstr "打印报告"
+
+msgid ""
+"This record has been modified\n"
+"do you want to save it?"
+msgstr ""
+"记录有改动\n"
+"是否保存?"
+
+msgid "Record saved."
+msgstr "记录保存成功."
+
+msgid "Working now on the duplicated record(s)."
+msgstr "正在处理重复记录."
+
+msgid "Are you sure to remove this record?"
+msgstr "确定要删除这条记录吗?"
+
+msgid "Are you sure to remove those records?"
+msgstr "确定要删除这些记录?"
+
+msgid "Records removed."
+msgstr "记录已成功删除."
+
+msgid "Records not removed."
+msgstr "所选记录未删除成功."
+
+msgid "You have to select one record."
+msgstr "需要选择一条记录."
+
+msgid "ID:"
+msgstr "标识:"
+
+msgid "Created by:"
+msgstr "创建者:"
+
+msgid "Created at:"
+msgstr "创建日期:"
+
+msgid "Edited by:"
+msgstr "编辑者:"
+
+msgid "Edited at:"
+msgstr "编辑于:"
+
+msgid "Model: "
+msgstr "模型: "
+
+msgid "Add..."
+msgstr "添加..."
+
+#, fuzzy
+msgid "Preview"
+msgstr "向前"
+
+msgid "Template"
+msgstr "模板"
+
+msgid "Attachment (%1)"
+msgstr "附件(%1)"
+
+msgid "Note (%1/%2)"
+msgstr "备注 (%1/%2)"
+
+msgid " of "
+msgstr " of "
+
+msgid "Filters"
+msgstr "筛选"
+
+msgid "Clear Search"
+msgstr "清除搜索"
+
+msgid "Bookmarks"
+msgstr "书签"
+
+msgid "Remove this bookmark"
+msgstr "移除书签"
+
+msgid "Bookmark this filter"
+msgstr "收藏当前筛选器"
+
+msgid "Bookmark Name:"
+msgstr "书签名称:"
+
+msgid "Show active records"
+msgstr "显示已启用记录"
+
+msgid "Show inactive records"
+msgstr "显示未启用记录"
+
+msgid "Find"
+msgstr "查找"
+
+msgid "From"
+msgstr "从"
+
+msgid "To"
+msgstr "至"
+
+msgid "Open the calendar"
+msgstr "打开日历"
+
+msgid "ID"
+msgstr "标识"
+
+msgid "Created by"
+msgstr "创建者"
+
+msgid "Created at"
+msgstr "创建日期"
+
+msgid "Modified by"
+msgstr "修改者"
+
+msgid "Modified at"
+msgstr "修改于"
+
+msgid "\"%1\" is required."
+msgstr "\"%1\" 是必填项."
+
+msgid "\"%1\" is not valid according to its domain."
+msgstr "根据域设置,\"%1\" 不可用."
+
+msgid "The values of \"%1\" are not valid."
+msgstr "\"%1\" 的取值不可用."
+
+msgid "Pre-validation"
+msgstr "预校验"
+
+msgid ":"
+msgstr ":"
+
+msgid "Translate"
+msgstr "翻译"
+
+msgid "Edit"
+msgstr "编辑"
+
+msgid "Fuzzy"
+msgstr "模糊"
+
+msgid "You need to save the record before adding translations."
+msgstr "请先保存已翻译条目,然后再增加新翻译 ."
+
+msgid "No other language available."
+msgstr "没有其他可翻译语言."
+
+msgid "Show"
+msgstr "显示"
+
+msgid "Hide"
+msgstr "隐藏"
+
+msgid "Open the record"
+msgstr "打开记录"
+
+msgid "Clear the field"
+msgstr "清除字段 "
+
+msgid "Search a record"
+msgstr "搜索记录"
+
+msgid "Add"
+msgstr "添加"
+
+msgid "Remove"
+msgstr "删除"
+
+msgid "Open"
+msgstr "打开"
+
+msgid "Undelete"
+msgstr "回滚"
+
+msgid "Save As"
+msgstr "另存为"
+
+msgid "Save As..."
+msgstr "另存为..."
+
+msgid "Select"
+msgstr "选择"
+
+msgid "Select..."
+msgstr "选择..."
+
+msgid "Clear"
+msgstr "清除"
+
+msgid "Choose a language"
+msgstr "选择一种语言"
+
+msgid "%1%"
+msgstr "%1%"
+
+msgid ": "
+msgstr ": "
+
+msgid "More"
+msgstr "更多"
+
+msgid "Today"
+msgstr "今天"
+
+msgid "Month"
+msgstr "月"
+
+msgid "Week"
+msgstr "周"
+
+msgid "Day"
+msgstr "天"
+
+msgid ",..."
+msgstr ",..."
+
+msgid "%1 (%2)"
+msgstr "%1 (%2)"
+
+msgid "Select your action"
+msgstr "选择一项操作"
+
+msgid "No action defined."
+msgstr "没有定义任何操作 ."
+
+msgid "Attachments (%1)"
+msgstr "附件(%1)"
+
+msgid "Notes (%1)"
+msgstr "注释(%s)"
+
+msgid "Search %1"
+msgstr "搜索 %1"
+
+msgid "Revision"
+msgstr "修订版本"
+
+msgid "All Fields"
+msgstr "所有字段"
+
+msgid "Fields Selected"
+msgstr "所选字段"
+
+msgid "CSV Parameters"
+msgstr "CSV 参数"
+
+msgid "Delimiter:"
+msgstr "分隔符:"
+
+msgid "Quote Char:"
+msgstr "限定符:"
+
+msgid "CSV Import: %1"
+msgstr "CSV导入: %s"
+
+msgid "Auto-Detect"
+msgstr "自动侦测"
+
+msgid "File to Import"
+msgstr "导入文件"
+
+msgid "Encoding:"
+msgstr "编码:"
+
+msgid "Lines to Skip:"
+msgstr "跳过行:"
+
+msgid "You must select an import file first."
+msgstr "必须先选择要导入的文件."
+
+msgid "An error occured in loading the file."
+msgstr "加载文件时发生错误。"
+
+msgid "Error processing the file at field %1."
+msgstr "处理字段 %s 对应的文件时出错."
+
+msgid "Error"
+msgstr "错误"
+
+msgid "Error occured in loading the file"
+msgstr "加载文件时发生错误"
+
+msgid "CSV Export: %1"
+msgstr "CSV导出:%s"
+
+msgid "Save Export"
+msgstr "保存导出"
+
+msgid "URL Export"
+msgstr "URL 导出"
+
+msgid "Delete Export"
+msgstr "删除导出"
+
+msgid "Predefined Exports"
+msgstr "预定义导出"
+
+msgid "Selected Records"
+msgstr "已选记录"
+
+msgid "Listed Records"
+msgstr "已列出记录"
+
+msgid "Export:"
+msgstr "导出:"
+
+msgid "Ignore search limit"
+msgstr "忽略搜索限制"
+
+msgid "Use locale format"
+msgstr "使用 locale 格式"
+
+msgid "Add Field Names"
+msgstr "添加字段名称"
+
+msgid "%1 (string)"
+msgstr "%1 (字符串)"
+
+msgid "%1 (model name)"
+msgstr "%1 (模型名称)"
+
+msgid "%1 (record name)"
+msgstr "%1 (记录名称)"
+
+msgid "What is the name of this export?"
+msgstr "导出设置的名称是?"
+
+msgid "Override %1 definition?"
+msgstr "覆盖 %1 的定义?"
+
+msgid "E-mail %1"
+msgstr "电子邮件 %1"
+
+msgid "To:"
+msgstr "收件人:"
+
+msgid "Cc:"
+msgstr "抄送:"
+
+msgid "Bcc:"
+msgstr "私下抄送:"
+
+msgid "Subject:"
+msgstr "主题:"
+
+msgid "Reports"
+msgstr "报告(_R)"
+
+msgid "Attachments"
+msgstr "附件"
+
+msgid "Files"
+msgstr ""
+
+msgid "Send"
+msgstr "发送"
+
+#, fuzzy
+msgid "Remove File"
+msgstr "删除"
+
+msgid "Wizard"
+msgstr "向导"
+
+msgid "Translate view"
+msgstr "翻译视图"
diff --git a/sao/package.json b/sao/package.json
new file mode 100644
index 0000000000..5985e82c5c
--- /dev/null
+++ b/sao/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "tryton-sao",
+ "title": "sao",
+ "description": "Tryton webclient",
+ "version": "6.0.25",
+ "homepage": "http://www.tryton.org/",
+ "author": {
+ "name": "Tryton"
+ },
+ "repository": {
+ "type": "hg",
+ "url": "http://hg.tryton.org/sao"
+ },
+ "bugs": {
+ "url": "http://bugs.tryton.org/",
+ "email": "bugs@tryton.org"
+ },
+ "license": "GPL-3.0+",
+ "files": [
+ "COPYRIGHT",
+ "CHANGELOG",
+ "Gruntfile.js",
+ "LICENSE",
+ "README.md",
+ "bower.json",
+ "dist",
+ "images",
+ "index.html",
+ "locale",
+ "package.json",
+ "src",
+ "tests"
+ ],
+ "scripts": {
+ "test": "npx grunt test",
+ "postinstall": "npx bower install"
+ },
+ "devDependencies": {
+ "grunt-contrib-nodeunit": "^2.0",
+ "grunt-contrib-qunit": "^3.1",
+ "grunt-contrib-watch": "^1.1",
+ "grunt-shell": "^3.0",
+ "grunt-xgettext": "^0.3",
+ "grunt-po2json": "^0.3"
+ },
+ "keywords": [
+ "tryton"
+ ],
+ "dependencies": {
+ "bower": "^1.8.4",
+ "grunt": "^1.0",
+ "grunt-cli": "^1.3",
+ "grunt-contrib-concat": "^1.0",
+ "grunt-contrib-jshint": "^2.1",
+ "grunt-contrib-less": "^2.0",
+ "grunt-contrib-uglify": "^4.0"
+ }
+}
diff --git a/sao/src/.jshintrc b/sao/src/.jshintrc
new file mode 100644
index 0000000000..5956712c68
--- /dev/null
+++ b/sao/src/.jshintrc
@@ -0,0 +1,5 @@
+{
+ "browser": true,
+ "devel": true,
+ "jquery": true
+}
diff --git a/sao/src/action.js b/sao/src/action.js
new file mode 100644
index 0000000000..1268e7b049
--- /dev/null
+++ b/sao/src/action.js
@@ -0,0 +1,240 @@
+/* This file is part of Tryton. The COPYRIGHT file at the top level of
+ this repository contains the full copyright notices and license terms. */
+(function() {
+ 'use strict';
+
+ Sao.Action = {
+ report_blob_url: undefined
+ };
+
+ Sao.Action.exec_action = function(action, data, context) {
+ if (!context) {
+ context = {};
+ } else {
+ context = jQuery.extend({}, context);
+ }
+ var session = Sao.Session.current_session;
+ if (data === undefined) {
+ data = {};
+ } else {
+ data = jQuery.extend({}, data);
+ }
+
+ delete context.active_id;
+ delete context.active_ids;
+ delete context.active_model;
+
+ function add_name_suffix(name, context){
+ if (!data.model || !data.ids) {
+ return jQuery.when(name);
+ }
+ var max_records = 5;
+ var ids = data.ids.filter(function(id){
+ return id >= 0;
+ }).slice(0, max_records);
+ if (!ids.length) {
+ return jQuery.when(name);
+ }
+ return Sao.rpc({
+ 'method': 'model.' + data.model + '.read',
+ 'params': [ids, ['rec_name'], context]
+ }, Sao.Session.current_session).then(function(result) {
+ var name_suffix = result.map(function(record){
+ return record.rec_name;
+ }).join(Sao.i18n.gettext(', '));
+
+ if (data.ids.length > ids.length) {
+ name_suffix += Sao.i18n.gettext(',...');
+ }
+ return Sao.i18n.gettext('%1 (%2)', name, name_suffix);
+ });
+ }
+ data.action_id = action.id;
+ var params = {};
+ var name_prm;
+ switch (action.type) {
+ case 'ir.action.act_window':
+ params.view_ids = [];
+ params.mode = null;
+ if (!jQuery.isEmptyObject(action.views)) {
+ params.view_ids = [];
+ params.mode = [];
+ action.views.forEach(function(x) {
+ params.view_ids.push(x[0]);
+ params.mode.push(x[1]);
+ });
+ } else if (!jQuery.isEmptyObject(action.view_id)) {
+ params.view_ids = [action.view_id[0]];
+ }
+
+ if (action.pyson_domain === undefined) {
+ action.pyson_domain = '[]';
+ }
+ var ctx = {
+ active_model: data.model || null,
+ active_id: data.id || null,
+ active_ids: data.ids || [],
+ };
+ ctx = jQuery.extend(ctx, session.context);
+ ctx._user = session.user_id;
+ var decoder = new Sao.PYSON.Decoder(ctx);
+ params.context = jQuery.extend(
+ {}, context,
+ decoder.decode( action.pyson_context || '{}'));
+ ctx = jQuery.extend(ctx, params.context);
+
+ ctx.context = ctx;
+ decoder = new Sao.PYSON.Decoder(ctx);
+ params.domain = decoder.decode(action.pyson_domain);
+ params.order = decoder.decode(action.pyson_order);
+ params.search_value = decoder.decode(
+ action.pyson_search_value || '[]');
+ params.tab_domain = [];
+ action.domains.forEach(function(element, index) {
+ params.tab_domain.push(
+ [element[0], decoder.decode(element[1]), element[2]]);
+ });
+ name_prm = jQuery.when(action.name);
+ params.model = action.res_model || data.res_model;
+ params.res_id = action.res_id || data.res_id;
+ params.context_model = action.context_model;
+ params.context_domain = action.context_domain;
+ if (action.limit !== null) {
+ params.limit = action.limit;
+ } else {
+ params.limit = Sao.config.limit;
+ }
+ params.icon = action['icon.rec_name'] || '';
+
+ if (action.keyword) {
+ name_prm = add_name_suffix(action.name, params.context);
+ }
+ name_prm.then(function(name) {
+ params.name = name;
+ Sao.Tab.create(params);
+ });
+ return;
+ case 'ir.action.wizard':
+ params.action = action.wiz_name;
+ params.data = data;
+ params.context = context;
+ params.window = action.window;
+ name_prm = jQuery.when(action.name);
+ if ((action.keyword || 'form_action') === 'form_action') {
+ name_prm = add_name_suffix(action.name, context);
+ }
+ name_prm.done(function(name) {
+ params.name = name;
+ Sao.Wizard.create(params);
+ });
+ return;
+ case 'ir.action.report':
+ params.name = action.report_name;
+ params.data = data;
+ params.direct_print = action.direct_print;
+ params.context = context;
+ Sao.Action.exec_report(params);
+ return;
+ case 'ir.action.url':
+ window.open(action.url, '_blank', 'noreferrer,noopener');
+ return;
+ }
+ };
+
+ Sao.Action.exec_keyword = function(keyword, data, context, warning,
+ alwaysask)
+ {
+ if (warning === undefined) {
+ warning = true;
+ }
+ if (alwaysask === undefined) {
+ alwaysask = false;
+ }
+ var actions = [];
+ var model_id = data.id;
+ var args = {
+ 'method': 'model.' + 'ir.action.keyword.get_keyword',
+ 'params': [keyword, [data.model, model_id], {}]
+ };
+ var prm = Sao.rpc(args, Sao.Session.current_session);
+ var exec_action = function(actions) {
+ var keyact = {};
+ for (var i in actions) {
+ var action = actions[i];
+ keyact[action.name.split(' / ').pop()] = action;
+ }
+ var prm = Sao.common.selection(
+ Sao.i18n.gettext('Select your action'),
+ keyact, alwaysask);
+ return prm.then(function(action) {
+ Sao.Action.exec_action(action, data, context);
+ }, function() {
+ if (jQuery.isEmptyObject(keyact) && warning) {
+ alert(Sao.i18n.gettext('No action defined.'));
+ }
+ });
+ };
+ return prm.pipe(exec_action);
+ };
+
+ Sao.Action.exec_report = function(attributes) {
+ if (!attributes.context) {
+ attributes.context = {};
+ }
+ var data = jQuery.extend({}, attributes.data);
+ var context = jQuery.extend({}, Sao.Session.current_session.context);
+ jQuery.extend(context, attributes.context);
+ context.direct_print = attributes.direct_print;
+
+ var prm = Sao.rpc({
+ 'method': 'report.' + attributes.name + '.execute',
+ 'params': [data.ids || [], data, context]
+ }, Sao.Session.current_session);
+ prm.done(function(result) {
+ var report_type = result[0];
+ var data = result[1];
+ var print = result[2];
+ var name = result[3];
+
+ // TODO direct print
+ var file_name = name + '.' + report_type;
+ Sao.common.download_file(data, file_name);
+ });
+ };
+
+ Sao.Action.execute = function(action, data, context, keyword) {
+ if (typeof action == 'number') {
+ action = Sao.rpc({
+ 'method': 'model.ir.action.get_action_value',
+ 'params': [action, context],
+ }, Sao.Session.current_session, false);
+ }
+ if (keyword) {
+ var keywords = {
+ 'ir.action.report': 'form_report',
+ 'ir.action.wizard': 'form_action',
+ 'ir.action.act_window': 'form_relate'
+ };
+ if (!action.keyword) {
+ action.keyword = keywords[action.type];
+ }
+ }
+ Sao.Action.exec_action(action, data, context);
+ };
+
+ Sao.Action.evaluate = function(action, atype, record) {
+ action = jQuery.extend({}, action);
+ var email = {};
+ if ('pyson_email' in action) {
+ email = record.expr_eval(action.pyson_email);
+ if (jQuery.isEmptyObject(email)) {
+ email = {};
+ }
+ }
+ if (!('subject' in email)) {
+ email.subject = action.name.replace(/_/g, '');
+ }
+ action.email = email;
+ return action;
+ };
+}());
diff --git a/sao/src/board.js b/sao/src/board.js
new file mode 100644
index 0000000000..02ee3414bb
--- /dev/null
+++ b/sao/src/board.js
@@ -0,0 +1,214 @@
+/* This file is part of Tryton. The COPYRIGHT file at the top level of
+ this repository contains the full copyright notices and license terms. */
+(function() {
+ 'use strict';
+
+ Sao.View.BoardXMLViewParser = Sao.class_(Sao.View.FormXMLViewParser, {
+ _parse_board: function(node, attributes) {
+ var container = new Sao.View.Form.Container(
+ Number(node.getAttribute('col') || 4));
+ this.view.el.append(container.el);
+ this.parse_child(node, container);
+ if (this._containers.length > 0) {
+ throw 'AssertionError';
+ }
+ },
+ _parse_action: function(node, attributes) {
+ var action;
+ if (attributes.yexpand === undefined) {
+ attributes.yexpand = true;
+ }
+ if (attributes.yfill === undefined) {
+ attributes.yfill = true;
+ }
+ action = new Sao.View.Board.Action(attributes, this.view.context);
+ this.view.actions.push(action);
+ this.container.add(action, attributes);
+ },
+ });
+
+ Sao.View.Board = Sao.class_(Object, {
+ xml_parser: Sao.View.BoardXMLViewParser,
+ init: function(xml, context) {
+ var attributes, attribute, node, actions_prms;
+
+ this.context = context;
+ this.actions = [];
+ this.containers = [];
+ this.state_widgets = [];
+ this.el = jQuery('', {
+ 'class': 'board'
+ });
+ new this.xml_parser(this, null, {}).parse(xml.children()[0]);
+
+ actions_prms = [];
+ for (var i = 0, len = this.actions.length; i < len; i++) {
+ actions_prms.push(this.actions[i].action_prm);
+ }
+ this.actions_prms = jQuery.when.apply(jQuery, actions_prms);
+ },
+ reload: function() {
+ var i;
+ for (i = 0; i < this.actions.length; i++) {
+ this.actions[i].display();
+ }
+ for (i = 0; i < this.state_widgets.length; i++) {
+ this.state_widgets[i].set_state(null);
+ }
+ }
+ });
+
+ Sao.View.Board.Action = Sao.class_(Object, {
+ init: function(attributes, context) {
+ if (context === undefined) {
+ context = {};
+ }
+
+ var session = Sao.Session.current_session;
+ this.name = attributes.name;
+
+ this.el = jQuery('', {
+ 'class': 'board-action panel panel-default',
+ });
+ this.title = jQuery('', {
+ 'class': 'panel-heading',
+ });
+ this.el.append(this.title);
+ this.body = jQuery('', {
+ 'class': 'panel-body',
+ });
+ this.el.append(this.body);
+
+ var act_window = new Sao.Model('ir.action.act_window');
+ this.action_prm = act_window.execute('get', [this.name], {});
+ this.action_prm.done(function(action) {
+ var params = {};
+ this.action = action;
+ params.view_ids = [];
+ params.mode = null;
+ if (!jQuery.isEmptyObject(action.views)) {
+ params.view_ids = [];
+ params.mode = [];
+ action.views.forEach(function(x) {
+ params.view_ids.push(x[0]);
+ params.mode.push(x[1]);
+ });
+ } else if (!jQuery.isEmptyObject(action.view_id)) {
+ params.view_ids = [action.view_id[0]];
+ }
+
+ if (!('pyson_domain' in this.action)) {
+ this.action.pyson_domain = '[]';
+ }
+ var ctx = {};
+ ctx = jQuery.extend(ctx, session.context);
+ ctx._user = session.user_id;
+ var decoder = new Sao.PYSON.Decoder(ctx);
+ params.context = jQuery.extend(
+ {}, context,
+ decoder.decode(action.pyson_context || '{}'));
+ ctx = jQuery.extend(ctx, params.context);
+
+ ctx.context = ctx;
+ decoder = new Sao.PYSON.Decoder(ctx);
+ params.domain = decoder.decode(action.pyson_domain);
+ params.order = decoder.decode(action.pyson_order);
+ params.search_value = decoder.decode(
+ action.pyson_search_value || '[]');
+ params.tab_domain = [];
+ action.domains.forEach(function(element, index) {
+ params.tab_domain.push(
+ [element[0], decoder.decode(element[1]), element[2]]);
+ });
+ params.context_model = action.context_model;
+ params.context_domain = action.context_domain;
+ if (action.limit !== null) {
+ params.limit = action.limit;
+ } else {
+ params.limit = Sao.config.limit;
+ }
+
+ this.context = ctx;
+ this.domain = [];
+ this.update_domain([]);
+
+ params.row_activate = this.row_activate.bind(this);
+
+ this.screen = new Sao.Screen(this.action.res_model,
+ params);
+
+ if (attributes.string) {
+ this.title.text(attributes.string);
+ } else {
+ this.title.text(this.action.name);
+ }
+ this.screen.switch_view().done(function() {
+ this.body.append(this.screen.screen_container.el);
+ this.screen.search_filter();
+ }.bind(this));
+ }.bind(this));
+ },
+ row_activate: function() {
+ var record_ids, win;
+
+ if (!this.screen.current_record) {
+ return;
+ }
+
+ if (this.screen.current_view.view_type == 'tree' &&
+ (this.screen.current_view.attributes.keyword_open == 1)) {
+ record_ids = this.screen.current_view.selected_records.map(
+ function(record) { return record.id; });
+ Sao.Action.exec_keyword('tree_open', {
+ model: this.screen.model_name,
+ id: this.screen.current_record.id,
+ ids: record_ids
+ }, jQuery.extend({}, this.screen.group._context), false);
+ } else {
+ win = new Sao.Window.Form(this.screen, function(result) {
+ if (result) {
+ this.screen.current_record.save();
+ } else {
+ this.screen.current_record.cancel();
+ }
+ }.bind(this), {
+ 'title': this.title.text(),
+ });
+ }
+ },
+ set_value: function() {
+ },
+ display: function() {
+ this.screen.search_filter(this.screen.screen_container.get_text());
+ },
+ get_active: function() {
+ if (this.screen && this.screen.current_record) {
+ return Sao.common.EvalEnvironment(this.screen.current_record);
+ }
+ },
+ update_domain: function(actions) {
+ var i, len;
+ var active, domain_ctx, decoder, new_domain;
+
+ domain_ctx = jQuery.extend({}, this.context);
+ domain_ctx.context = domain_ctx;
+ domain_ctx._user = Sao.Session.current_session.user_id;
+ for (i = 0, len = actions.length; i < len; i++) {
+ active = actions[i].get_active();
+ if (active) {
+ domain_ctx[actions[i].name] = active;
+ }
+ }
+ decoder = new Sao.PYSON.Decoder(domain_ctx);
+ new_domain = decoder.decode(this.action.pyson_domain);
+ if (Sao.common.compare(this.domain, new_domain)) {
+ return;
+ }
+ this.domain.splice(0, this.domain.length);
+ jQuery.extend(this.domain, new_domain);
+ if (this.screen) {
+ this.display();
+ }
+ }
+ });
+}());
diff --git a/sao/src/bus.js b/sao/src/bus.js
new file mode 100644
index 0000000000..20e31ce435
--- /dev/null
+++ b/sao/src/bus.js
@@ -0,0 +1,86 @@
+/* This file is part of Tryton. The COPYRIGHT file at the top level of
+ this repository contains the full copyright notices and license terms. */
+(function() {
+ 'use strict';
+
+ Sao.Bus = {};
+
+ // Bus Identifier
+ Sao.Bus.id = Sao.common.uuid4();
+ Sao.Bus.channels = ['client:' + Sao.Bus.id];
+
+ Sao.Bus.listen = function(last_message, wait) {
+ wait = wait || 1;
+ var session = Sao.Session.current_session;
+ if (!session) {
+ return;
+ }
+
+ var prm = jQuery.ajax({
+ headers: {
+ Authorization: 'Session ' + session.get_auth(),
+ },
+ contentType: 'application/json',
+ data: JSON.stringify({
+ last_message: last_message,
+ channels: Sao.Bus.channels
+ }),
+ dataType: 'json',
+ url: '/' + session.database + '/bus',
+ type: 'POST',
+ timeout: Sao.config.bus_timeout,
+ });
+
+ prm.done(function(response) {
+ if (Sao.Session.current_session != session) {
+ return;
+ }
+ if (response.message) {
+ last_message = response.message.message_id;
+ Sao.Bus.handle(response.message);
+ }
+ Sao.Bus.listen(last_message, 1);
+ });
+
+ prm.fail(function(response, status, error) {
+ if (Sao.Session.current_session != session) {
+ return;
+ }
+ if (error === "timeout") {
+ Sao.Bus.listen(last_message, 1);
+ } else if (response.status == 501) {
+ console.log("Bus not supported");
+ return;
+ } else {
+ window.setTimeout(
+ Sao.Bus.listen,
+ Math.min(wait * 1000, Sao.config.bus_timeout),
+ last_message, wait * 2);
+ }
+ });
+ };
+
+ Sao.Bus.handle = function(message) {
+ var notify = function(message) {
+ try {
+ if (Notification.permission != "granted") {
+ return;
+ }
+ } catch (e) {
+ (console.error || console.log).call(console, e, e.stack);
+ return;
+ }
+
+ new Notification(message.title, {
+ body: message.body || '',
+ });
+ };
+
+ switch (message.type) {
+ case 'notification':
+ notify(message);
+ break;
+ }
+ };
+
+}());
diff --git a/sao/src/common.js b/sao/src/common.js
new file mode 100644
index 0000000000..ef125469cf
--- /dev/null
+++ b/sao/src/common.js
@@ -0,0 +1,4115 @@
+/* This file is part of Tryton. The COPYRIGHT file at the top level of
+ this repository contains the full copyright notices and license terms. */
+(function() {
+ 'use strict';
+
+ Sao.common = {};
+
+ Sao.common.BACKSPACE_KEYCODE = 8;
+ Sao.common.TAB_KEYCODE = 9;
+ Sao.common.RETURN_KEYCODE = 13;
+ Sao.common.ESC_KEYCODE = 27;
+ Sao.common.UP_KEYCODE = 38;
+ Sao.common.DOWN_KEYCODE = 40;
+ Sao.common.DELETE_KEYCODE = 46;
+ Sao.common.F2_KEYCODE = 113;
+ Sao.common.F3_KEYCODE = 114;
+
+ Sao.common.SELECTION_NONE = 1;
+ Sao.common.SELECTION_SINGLE = 2;
+ Sao.common.SELECTION_MULTIPLE = 3;
+
+ Sao.common.BIG_IMAGE_SIZE = Math.pow(10, 6);
+
+ Sao.common.compare = function(arr1, arr2) {
+ if (arr1.length != arr2.length) {
+ return false;
+ }
+ for (var i = 0; i < arr1.length; i++) {
+ var a = arr1[i], b = arr2[i];
+ if ((a instanceof Array) && (b instanceof Array)) {
+ if (!Sao.common.compare(a, b)) {
+ return false;
+ }
+ } else if (moment.isMoment(a) && moment.isMoment(b)) {
+ if ((a.isDate != b.isDate) &&
+ (a.isDateTime != b.isDateTime) &&
+ (a.valueOf() != b.valueOf())) {
+ return false;
+ }
+ } else if ((a instanceof Number) || (b instanceof Number)) {
+ if (Number(a) !== Number(b)) {
+ return false;
+ }
+ } else if (a != b) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ Sao.common.contains = function(array1, array2) {
+ for (var i = 0; i < array1.length; i++) {
+ if (Sao.common.compare(array1[i], array2)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Find the intersection of two arrays.
+ // The arrays must be sorted.
+ Sao.common.intersect = function(a, b) {
+ var ai = 0, bi = 0;
+ var result = [];
+ while (ai < a.length && bi < b.length) {
+ if (a[ai] < b[bi]) {
+ ai++;
+ } else if (a[ai] > b[bi]) {
+ bi++;
+ } else {
+ result.push(a[ai]);
+ ai++;
+ bi++;
+ }
+ }
+ return result;
+ };
+
+ Sao.common.scrollIntoViewIfNeeded = function(element) {
+ element = element[0];
+ var rect = element.getBoundingClientRect();
+ if (rect.bottom > window.innerHeight) {
+ element.scrollIntoView(false);
+ }
+ if (rect.top < 0) {
+ element.scrollIntoView();
+ }
+ };
+
+ // Handle click and Return press event
+ // If one, the handler is executed at most once for both events
+ Sao.common.click_press = function(func, one) {
+ return function handler(evt) {
+ if (evt.type != 'keypress' ||
+ evt.which == Sao.common.RETURN_KEYCODE) {
+ if (one) {
+ jQuery(this).off('click keypress', null, handler);
+ }
+ return func(evt);
+ }
+ };
+ };
+
+ // Cartesian product
+ Sao.common.product = function(array, repeat) {
+ repeat = repeat || 1;
+ var pools = [];
+ var i = 0;
+ while (i < repeat) {
+ pools = pools.concat(array);
+ i++;
+ }
+ var result = [[]];
+ pools.forEach(function(pool) {
+ var tmp = [];
+ result.forEach(function(x) {
+ pool.forEach(function(y) {
+ tmp.push(x.concat([y]));
+ });
+ });
+ result = tmp;
+ });
+ return result;
+ };
+
+ Sao.common.selection = function(title, values, alwaysask) {
+ if (alwaysask === undefined) {
+ alwaysask = false;
+ }
+ var prm = jQuery.Deferred();
+ if (jQuery.isEmptyObject(values)) {
+ prm.reject();
+ return prm;
+ }
+ var keys = Object.keys(values).sort();
+ if ((keys.length == 1) && (!alwaysask)) {
+ var key = keys[0];
+ prm.resolve(values[key]);
+ return prm;
+ }
+ var dialog = new Sao.Dialog(
+ title || Sao.i18n.gettext('Your selection:'),
+ 'selection-dialog');
+
+ keys.forEach(function(k, i) {
+ jQuery('', {
+ 'class': 'radio'
+ }).append(jQuery('')
+ .text(' ' + k)
+ .prepend(jQuery('', {
+ 'type': 'radio',
+ 'name': 'selection',
+ 'value': i
+ })))
+ .appendTo(dialog.body);
+ });
+ dialog.body.find('input').first().prop('checked', true);
+
+ jQuery('', {
+ 'class': 'btn btn-link',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('Cancel')).click(function() {
+ dialog.modal.modal('hide');
+ prm.reject();
+ }).appendTo(dialog.footer);
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('OK')).click(function() {
+ var i = dialog.body.find('input:checked').attr('value');
+ dialog.modal.modal('hide');
+ prm.resolve(values[keys[i]]);
+ }).appendTo(dialog.footer);
+ dialog.modal.on('hidden.bs.modal', function(e) {
+ jQuery(this).remove();
+ });
+ dialog.modal.modal('show');
+ return prm;
+ };
+
+ Sao.common.moment_format = function(format) {
+ return format
+ .replace('%a', 'ddd')
+ .replace('%A', 'dddd')
+ .replace('%w', 'd')
+ .replace('%d', 'DD')
+ .replace('%b', 'MMM')
+ .replace('%B', 'MMMM')
+ .replace('%m', 'MM')
+ .replace('%y', 'YY')
+ .replace('%Y', 'YYYY')
+ .replace('%H', 'HH')
+ .replace('%I', 'hh')
+ .replace('%p', 'A')
+ .replace('%M', 'mm')
+ .replace('%S', 'ss')
+ .replace('%f', 'SSS')
+ .replace('%z', 'ZZ')
+ .replace('%Z', 'zz')
+ .replace('%j', 'DDDD')
+ .replace('%U', 'ww')
+ .replace('%W', 'WW')
+ .replace('%c', 'llll')
+ .replace('%x', 'L')
+ .replace('%X', 'LTS')
+ .replace('%', '%%')
+ ;
+ };
+
+ Sao.common.DATE_OPERATORS = [
+ ['S', moment.duration(-1, 'seconds')],
+ ['s', moment.duration(1, 'seconds')],
+ ['I', moment.duration(-1, 'minutes')],
+ ['i', moment.duration(1, 'minutes')],
+ ['H', moment.duration(-1, 'hours')],
+ ['h', moment.duration(1, 'hours')],
+ ['D', moment.duration(-1, 'days')],
+ ['d', moment.duration(1, 'days')],
+ ['W', moment.duration(-1, 'weeks')],
+ ['w', moment.duration(1, 'weeks')],
+ ['M', moment.duration(-1, 'months')],
+ ['m', moment.duration(1, 'months')],
+ ['Y', moment.duration(-1, 'years')],
+ ['y', moment.duration(1, 'years')],
+ ];
+
+ Sao.common.date_format = function(format) {
+ if (jQuery.isEmptyObject(format)) {
+ format = '%x';
+ if (Sao.Session.current_session) {
+ var context = Sao.Session.current_session.context;
+ if (context.locale && context.locale.date) {
+ format = context.locale.date;
+ }
+ }
+ }
+ return Sao.common.moment_format(format);
+ };
+
+ Sao.common.format_time = function(format, date) {
+ if (!date) {
+ return '';
+ }
+ return date.format(Sao.common.moment_format(format));
+ };
+
+ Sao.common.parse_time = function(format, value) {
+ if (!value) {
+ return null;
+ }
+ var getNumber = function(pattern) {
+ var i = format.indexOf(pattern);
+ if (~i) {
+ var number = parseInt(value.slice(i, i + pattern.length), 10);
+ if (!isNaN(number)) {
+ return number;
+ }
+ }
+ return 0;
+ };
+ return Sao.Time(getNumber('%H'), getNumber('%M'), getNumber('%S'),
+ getNumber('%f'));
+ };
+
+ Sao.common.format_date = function(date_format, date) {
+ if (!date) {
+ return '';
+ }
+ return date.format(Sao.common.moment_format(date_format));
+ };
+
+ Sao.common.parse_date = function(date_format, value) {
+ var date = moment(value,
+ Sao.common.moment_format(date_format));
+ if (date.isValid()) {
+ date = Sao.Date(date.year(), date.month(), date.date());
+ } else {
+ date = null;
+ }
+ return date;
+ };
+
+ Sao.common.format_datetime = function(datetime_format, date) {
+ if (!date) {
+ return '';
+ }
+ return date.format(Sao.common.moment_format(datetime_format));
+ };
+
+ Sao.common.parse_datetime = function(datetime_format, value) {
+ var date = moment(value, Sao.common.moment_format(datetime_format));
+ if (date.isValid()) {
+ date = Sao.DateTime(date.year(), date.month(), date.date(),
+ date.hour(), date.minute(), date.second(),
+ date.millisecond());
+ } else {
+ date = null;
+ }
+ return date;
+ };
+
+ Sao.common.timedelta = {};
+ Sao.common.timedelta.DEFAULT_CONVERTER = {
+ 's': 1
+ };
+ Sao.common.timedelta.DEFAULT_CONVERTER.m =
+ Sao.common.timedelta.DEFAULT_CONVERTER.s * 60;
+ Sao.common.timedelta.DEFAULT_CONVERTER.h =
+ Sao.common.timedelta.DEFAULT_CONVERTER.m * 60;
+ Sao.common.timedelta.DEFAULT_CONVERTER.d =
+ Sao.common.timedelta.DEFAULT_CONVERTER.h * 24;
+ Sao.common.timedelta.DEFAULT_CONVERTER.w =
+ Sao.common.timedelta.DEFAULT_CONVERTER.d * 7;
+ Sao.common.timedelta.DEFAULT_CONVERTER.M =
+ Sao.common.timedelta.DEFAULT_CONVERTER.d * 30;
+ Sao.common.timedelta.DEFAULT_CONVERTER.Y =
+ Sao.common.timedelta.DEFAULT_CONVERTER.d * 365;
+ Sao.common.timedelta._get_separator = function() {
+ return {
+ Y: Sao.i18n.gettext('Y'),
+ M: Sao.i18n.gettext('M'),
+ w: Sao.i18n.gettext('w'),
+ d: Sao.i18n.gettext('d'),
+ h: Sao.i18n.gettext('h'),
+ m: Sao.i18n.gettext('m'),
+ s: Sao.i18n.gettext('s')
+ };
+ };
+ Sao.common.timedelta.format = function(value, converter) {
+ if (!value) {
+ return '';
+ }
+ if (!converter) {
+ converter = Sao.common.timedelta.DEFAULT_CONVERTER;
+ }
+ var text = [];
+ value = value.asSeconds();
+ var sign = '';
+ if (value < 0) {
+ sign = '-';
+ }
+ value = Math.abs(value);
+ converter = Object.keys(
+ Sao.common.timedelta._get_separator()).map(function(key) {
+ return [key, converter[key]];
+ });
+ var values = [];
+ var k, v;
+ for (var i = 0; i < converter.length; i++) {
+ k = converter[i][0];
+ v = converter[i][1];
+ if (v) {
+ var part = Math.floor(value / v);
+ value -= part * v;
+ values.push(part);
+ } else {
+ values.push(0);
+ }
+ }
+ for (i = 0; i < converter.length - 3; i++) {
+ k = converter[i][0];
+ v = values[i];
+ if (v) {
+ text.push(v + Sao.common.timedelta._get_separator()[k]);
+ }
+ }
+ if (jQuery(values.slice(-3)).is(function(i, v) { return v; }) ||
+ jQuery.isEmptyObject(text)) {
+ var time_values = values.slice(-3);
+ var time = time_values[0].toString().padStart(2, "0");
+ time += ":" + time_values[1].toString().padStart(2, "0");
+ if (time_values[2] || value) {
+ time += ':' + time_values[2].toString().padStart(2, "0");
+ }
+ text.push(time);
+ }
+ text = sign + text.reduce(function(p, c) {
+ if (p) {
+ return p + ' ' + c;
+ } else {
+ return c;
+ }
+ });
+ if (value) {
+ if (!jQuery(values.slice(-3)).is(function(i, v) { return v; })) {
+ // Add space if no time
+ text += ' ';
+ }
+ text += ('' + value.toFixed(6)).slice(1);
+ }
+ return text;
+ };
+ Sao.common.timedelta.parse = function(text, converter) {
+ if (!text) {
+ return null;
+ }
+ if (!converter) {
+ converter = Sao.common.timedelta.DEFAULT_CONVERTER;
+ }
+ var separators = Sao.common.timedelta._get_separator();
+ var separator;
+ for (var k in separators) {
+ separator = separators[k];
+ text = text.replace(separator, separator + ' ');
+ }
+
+ var seconds = 0;
+ var sec;
+ var parts = text.split(' ');
+ for (var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ if (part.contains(':')) {
+ var subparts = part.split(':');
+ var subconverter = [
+ converter.h, converter.m, converter.s];
+ for (var j = 0;
+ j < Math.min(subparts.length, subconverter.length);
+ j ++) {
+ var t = subparts[j];
+ var v = subconverter[j];
+ sec = Math.abs(parseFloat(t)) * v;
+ if (!isNaN(sec)) {
+ seconds += sec;
+ }
+ }
+ } else {
+ var found = false;
+ for (var key in separators) {
+ separator =separators[key];
+ if (part.endsWith(separator)) {
+ part = part.slice(0, -separator.length);
+ sec = Math.abs(parseFloat(part)) * converter[key];
+ if (!isNaN(sec)) {
+ seconds += sec;
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ sec = Math.abs(parseFloat(part));
+ if (!isNaN(sec)) {
+ seconds += sec;
+ }
+ }
+ }
+ }
+ if (text.contains('-')) {
+ seconds *= -1;
+ }
+ return Sao.TimeDelta(null, seconds);
+ };
+
+ Sao.common.ModelAccess = Sao.class_(Object, {
+ init: function() {
+ this.batchnum = 100;
+ this._models = [];
+ this._access = {};
+ },
+ load_models: function(refresh) {
+ if (!refresh) {
+ this._access = {};
+ }
+ this._models = Sao.rpc({
+ 'method': 'model.ir.model.list_models',
+ 'params': [{}]
+ }, Sao.Session.current_session, false);
+ },
+ get: function(model) {
+ if (this._access[model] !== undefined) {
+ return this._access[model];
+ }
+ var idx = this._models.indexOf(model);
+ if (idx < 0) {
+ this.load_models(false);
+ idx = this._models.indexOf(model);
+ }
+ var to_load = this._models.slice(
+ Math.max(0, idx - Math.floor(this.batchnum / 2)),
+ idx + Math.floor(this.batchnum / 2));
+ var access = Sao.rpc({
+ 'method': 'model.ir.model.access.get_access',
+ 'params': [to_load, {}]
+ }, Sao.Session.current_session, false);
+ this._access = jQuery.extend(this._access, access);
+ return this._access[model];
+ }
+ });
+ Sao.common.MODELACCESS = new Sao.common.ModelAccess();
+
+ Sao.common.ModelHistory = Sao.class_(Object, {
+ init: function() {
+ this._models = [];
+ },
+ load_history: function() {
+ this._models = [];
+ return Sao.rpc({
+ 'method': 'model.ir.model.list_history',
+ 'params': [{}]
+ }, Sao.Session.current_session).then(function(models) {
+ this._models = models;
+ }.bind(this));
+ },
+ contains: function(model) {
+ return ~this._models.indexOf(model);
+ }
+ });
+ Sao.common.MODELHISTORY = new Sao.common.ModelHistory();
+
+ Sao.common.ModelName = Sao.class_(Object, {
+ init: function() {
+ this._names = {};
+ },
+ load_names: function() {
+ this._names = Sao.rpc({
+ 'method':'model.ir.model.get_names',
+ 'params': [{}],
+ }, Sao.Session.current_session, false);
+ },
+ get: function(model) {
+ if (jQuery.isEmptyObject(this._names)) {
+ this.load_names();
+ }
+ return this._names[model] || '';
+ },
+ clear: function() {
+ this._names = {};
+ },
+ });
+ Sao.common.MODELNAME = new Sao.common.ModelName();
+
+ Sao.common.ViewSearch = Sao.class_(Object, {
+ init: function() {
+ this.encoder = new Sao.PYSON.Encoder();
+ },
+ load_searches: function() {
+ this.searches = {};
+ return Sao.rpc({
+ 'method': 'model.ir.ui.view_search.get_search',
+ 'params': [{}]
+ }, Sao.Session.current_session).then(function(searches) {
+ this.searches = searches;
+ }.bind(this));
+ },
+ get: function(model) {
+ return this.searches[model] || [];
+ },
+ add: function(model, name, domain) {
+ return Sao.rpc({
+ 'method': 'model.ir.ui.view_search.create',
+ 'params': [[{
+ 'model': model,
+ 'name': name,
+ 'domain': this.encoder.encode(domain)
+ }], {}]
+ }, Sao.Session.current_session).then(function(ids) {
+ var id = ids[0];
+ if (this.searches[model] === undefined) {
+ this.searches[model] = [];
+ }
+ this.searches[model].push([id, name, domain, true]);
+ }.bind(this));
+ },
+ remove: function(model, id) {
+ return Sao.rpc({
+ 'method': 'model.ir.ui.view_search.delete',
+ 'params': [[id], {}]
+ }, Sao.Session.current_session).then(function() {
+ for (var i = 0; i < this.searches[model].length; i++) {
+ var domain = this.searches[model][i];
+ if (domain[0] === id) {
+ this.searches[model].splice(i, 1);
+ break;
+ }
+ }
+ }.bind(this));
+ }
+ });
+ Sao.common.VIEW_SEARCH = new Sao.common.ViewSearch();
+
+ Sao.common.humanize = function(size) {
+ var sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ for (var i =0, len = sizes.length; i < len; i++) {
+ if (size < 1000) {
+ return size.toPrecision(4) + ' ' + sizes[i];
+ }
+ size /= 1000;
+ }
+ };
+
+ Sao.common.EvalEnvironment = function(parent_, eval_type) {
+ if (eval_type === undefined)
+ eval_type = 'eval';
+ var environment;
+ if (eval_type == 'eval') {
+ environment = parent_.get_eval();
+ } else {
+ environment = {};
+ for (var key in parent_.model.fields) {
+ var field = parent_.model.fields[key];
+ environment[key] = field.get_on_change_value(parent_);
+ }
+ }
+ environment.id = parent_.id;
+ if (parent_.group.parent)
+ Object.defineProperty(environment, '_parent_' +
+ parent_.group.parent_name, {
+ 'enumerable': true,
+ 'get': function() {
+ return Sao.common.EvalEnvironment(parent_.group.parent,
+ eval_type);
+ }
+ });
+ environment.get = function(item, default_) {
+ if (this.hasOwnProperty(item))
+ return this[item];
+ return default_;
+ };
+
+ return environment;
+ };
+
+ Sao.common.selection_mixin = {};
+ Sao.common.selection_mixin.init = function() {
+ this.selection = null;
+ this.help = null;
+ this.inactive_selection = [];
+ this._last_domain = null;
+ this._values2selection = {};
+ this._domain_cache = {};
+ if (this.nullable_widget === undefined) {
+ this.nullable_widget = true;
+ }
+ };
+ Sao.common.selection_mixin.init_selection = function(value, callback) {
+ if (!value) {
+ value = {};
+ (this.attributes.selection_change_with || []).forEach(function(e) {
+ value[e] = null;
+ });
+ }
+ var key = JSON.stringify(value);
+ var selection = this.attributes.selection || [];
+ var help = this.attributes.help_selection || {};
+ var prm;
+ var prepare_selection = function(selection) {
+ selection = jQuery.extend([], selection);
+ if (this.attributes.sort === undefined || this.attributes.sort) {
+ selection.sort(function(a, b) {
+ return a[1].localeCompare(b[1]);
+ });
+ }
+ this.selection = jQuery.extend([], selection);
+ this.help = this.attributes.help_selection || {};
+ if (callback) callback(this.selection, this.help);
+ };
+ if (!(selection instanceof Array) &&
+ !(key in this._values2selection)) {
+ if (!jQuery.isEmptyObject(this.attributes.selection_change_with)) {
+ prm = this.model.execute(selection, [value]);
+ } else {
+ prm = this.model.execute(selection, []);
+ }
+ prm = prm.then(function(selection) {
+ this._values2selection[key] = selection;
+ return selection;
+ }.bind(this));
+ prm = prm.then(prepare_selection.bind(this));
+ } else {
+ if (key in this._values2selection) {
+ selection = this._values2selection[key];
+ }
+ prepare_selection.call(this, selection);
+ prm = jQuery.when();
+ }
+ this.inactive_selection = [];
+ this._selection_prm = prm;
+ };
+ Sao.common.selection_mixin.update_selection = function(record, field,
+ callback) {
+ var _update_selection = function() {
+ if (!field) {
+ if (callback) {
+ callback(this.selection, this.help);
+ }
+ return;
+ }
+ var domain = field.get_domain(record);
+ if (!('relation' in this.attributes)) {
+ var change_with = this.attributes.selection_change_with || [];
+ var value = record._get_on_change_args(change_with);
+ delete value.id;
+ Sao.common.selection_mixin.init_selection.call(this, value,
+ function() {
+ Sao.common.selection_mixin.filter_selection.call(
+ this, domain, record, field);
+ if (callback) {
+ callback(this.selection, this.help);
+ }
+ }.bind(this));
+ } else {
+ var context = field.get_context(record);
+ var jdomain = JSON.stringify([domain, context]);
+ if (jdomain in this._domain_cache) {
+ this.selection = this._domain_cache[jdomain];
+ this._last_domain = [domain, context];
+ }
+ if ((this._last_domain !== null) &&
+ Sao.common.compare(domain, this._last_domain[0]) &&
+ (JSON.stringify(context) ==
+ JSON.stringify(this._last_domain[1]))) {
+ if (callback) {
+ callback(this.selection, this.help);
+ }
+ return;
+ }
+ var fields = ['rec_name'];
+ var help_field = this.attributes.help_field;
+ if (help_field) {
+ fields.push(help_field);
+ }
+ var prm = Sao.rpc({
+ 'method': 'model.' + this.attributes.relation +
+ '.search_read',
+ 'params': [domain, 0, null, null, fields, context]
+ }, record.model.session);
+ prm.done(function(result) {
+ var selection = [];
+ result.forEach(function(x) {
+ selection.push([x.id, x.rec_name]);
+ });
+ if (this.nullable_widget) {
+ selection.push([null, '']);
+ }
+ var help = {};
+ if (help_field){
+ result.forEach(function(x) {
+ help[x.id] = x[help_field];
+ });
+ }
+ this._last_domain = [domain, context];
+ this._domain_cache[jdomain] = selection;
+ this.selection = jQuery.extend([], selection);
+ this.help = help;
+ if (callback) {
+ callback(this.selection, this.help);
+ }
+ }.bind(this));
+ prm.fail(function() {
+ this._last_domain = null;
+ this.selection = [];
+ if (callback) {
+ callback(this.selection, this.help);
+ }
+ }.bind(this));
+ }
+ };
+ this._selection_prm.done(_update_selection.bind(this));
+ };
+ Sao.common.selection_mixin.filter_selection = function(
+ domain, record, field) {
+ if (jQuery.isEmptyObject(domain)) {
+ return;
+ }
+
+ var inversion = new Sao.common.DomainInversion();
+ var _value_evaluator = function(value) {
+ var context = {};
+ context[this.field_name] = value[0];
+ return inversion.eval_domain(domain, context);
+ }.bind(this);
+
+ var _model_evaluator = function(allowed_models) {
+ return function(value) {
+ return ~allowed_models.indexOf(value[0]) ||
+ jQuery.isEmptyObject(allowed_models);
+ };
+ };
+
+ var evaluator;
+ var type_ = field.description.type;
+ if (type_ == 'reference') {
+ var allowed_models = field.get_models(record);
+ evaluator = _model_evaluator(allowed_models);
+ } else if (type_ == 'multiselection') {
+ return;
+ } else {
+ evaluator = _value_evaluator;
+ }
+
+ this.selection = this.selection.filter(evaluator);
+ };
+ Sao.common.selection_mixin.get_inactive_selection = function(value) {
+ if (!this.attributes.relation) {
+ return jQuery.when([]);
+ }
+ for (var i = 0, len = this.inactive_selection.length; i < len; i++) {
+ if (value == this.inactive_selection[i][0]) {
+ return jQuery.when(this.inactive_selection[i]);
+ }
+ }
+ var prm = Sao.rpc({
+ 'method': 'model.' + this.attributes.relation + '.read',
+ 'params': [[value], ['rec_name'], {}]
+ }, Sao.Session.current_session);
+ return prm.then(function(result) {
+ this.inactive_selection.push([result[0].id, result[0].rec_name]);
+ return [result[0].id, result[0].rec_name];
+ }.bind(this));
+ };
+
+ Sao.common.Button = Sao.class_(Object, {
+ init: function(attributes, el, size, style) {
+ this.attributes = attributes;
+ if (el) {
+ this.el = el;
+ } else {
+ this.el = jQuery('', {
+ title: attributes.string || '',
+ name: attributes.name || '',
+ });
+ this.el.text(attributes.string || '');
+ if (this.attributes.rule) {
+ this.el.append(' ').append(jQuery('', {
+ 'class': 'badge'
+ }));
+ }
+ }
+ this.icon = this.el.children('img');
+ if (!this.icon.length) {
+ this.icon = jQuery('', {
+ 'class': 'icon',
+ }).prependTo(this.el);
+ this.icon.hide();
+ }
+ this.el.addClass(
+ ['btn', (style || 'btn-default'), (size || '')].join(' '));
+ this.el.attr('type', 'button');
+ this.icon.attr('aria-hidden', true);
+ this.set_icon(attributes.icon);
+ },
+ set_icon: function(icon_name) {
+ if (!icon_name) {
+ this.icon.attr('src', '');
+ this.icon.hide();
+ return;
+ }
+ Sao.common.ICONFACTORY.get_icon_url(icon_name).done(function(url) {
+ this.icon.attr('src', url);
+ this.icon.show();
+ }.bind(this));
+ },
+ set_state: function(record) {
+ var states;
+ if (record) {
+ states = record.expr_eval(this.attributes.states || {});
+ } else {
+ states = {};
+ }
+ if (states.invisible) {
+ this.el.hide();
+ } else {
+ this.el.show();
+ }
+ this.el.prop('disabled', Boolean(states.readonly));
+ this.set_icon(states.icon || this.attributes.icon);
+
+ if (this.attributes.rule) {
+ var prm;
+ if (record) {
+ prm = record.get_button_clicks(this.attributes.name);
+ } else {
+ prm = jQuery.when();
+ }
+ prm.then(function(clicks) {
+ var counter = this.el.children('.badge');
+ var users = [];
+ var tip = '';
+ if (!jQuery.isEmptyObject(clicks)) {
+ for (var u in clicks) {
+ users.push(clicks[u]);
+ }
+ tip = Sao.i18n.gettext('By: ') +
+ users.join(Sao.i18n.gettext(', '));
+ }
+ counter.text(users.length || '');
+ counter.attr('title', tip);
+ }.bind(this));
+ }
+
+ if (((this.attributes.type === undefined) ||
+ (this.attributes.type === 'class')) && (record)) {
+ var parent = record.group.parent;
+ while (parent) {
+ if (parent.has_changed()) {
+ this.el.prop('disabled', true);
+ break;
+ }
+ parent = parent.group.parent;
+ }
+ }
+ }
+ });
+
+ Sao.common.udlex = Sao.class_(Object, {
+ init: function(instream) {
+
+ var Stream = Sao.class_(Object, {
+ init: function(stream) {
+ this.stream = stream.split('');
+ this.i = 0;
+ },
+ read: function(length) {
+ if (length === undefined) {
+ length = 1;
+ }
+ if (this.i >= this.stream.length) {
+ return null;
+ }
+ var value = this.stream
+ .slice(this.i, this.i + length).join();
+ this.i += length;
+ return value;
+ }
+ });
+ this.instream = new Stream(instream);
+ this.eof = null;
+ this.commenters = '';
+ this.nowordchars = [':', '>', '<', '=', '!', '"', ';', '(', ')'];
+ this.whitespace = ' \t\r\n';
+ this.whitespace_split = false;
+ this.quotes = '"';
+ this.escape = '\\';
+ this.escapedquotes = '"';
+ this.state = ' ';
+ this.pushback = [];
+ this.token = '';
+ },
+ get_token: function() {
+ if (this.pushback.length > 0) {
+ return this.pushback.shift();
+ }
+ var raw = this.read_token();
+ return raw;
+ },
+ read_token: function() {
+ var quoted = false;
+ var escapedstate = ' ';
+ while (true) {
+ var nextchar = this.instream.read(1);
+ if (this.state === null) {
+ this.token = ''; // past en of file
+ break;
+ } else if (this.state == ' ') {
+ if (!nextchar) {
+ this.state = null; // end of file
+ break;
+ } else if (this.whitespace.contains(nextchar)) {
+ if (this.token || quoted) {
+ break; // emit current token
+ } else {
+ continue;
+ }
+ } else if (this.commenters.contains(nextchar)) {
+ // TODO readline
+ } else if (this.escape.contains(nextchar)) {
+ escapedstate = 'a';
+ this.state = nextchar;
+ } else if (!~this.nowordchars.indexOf(nextchar)) {
+ this.token = nextchar;
+ this.state = 'a';
+ } else if (this.quotes.contains(nextchar)) {
+ this.state = nextchar;
+ } else if (this.whitespace_split) {
+ this.token = nextchar;
+ this.state = 'a';
+ } else {
+ this.token = nextchar;
+ if (this.token || quoted) {
+ break; // emit current token
+ } else {
+ continue;
+ }
+ }
+ } else if (this.quotes.contains(this.state)) {
+ quoted = true;
+ if (!nextchar) { // end of file
+ throw 'no closing quotation';
+ }
+ if (nextchar == this.state) {
+ this.state = 'a';
+ } else if (this.escape.contains(nextchar) &&
+ this.escapedquotes.contains(this.state)) {
+ escapedstate = this.state;
+ this.state = nextchar;
+ } else {
+ this.token = this.token + nextchar;
+ }
+ } else if (this.escape.contains(this.state)) {
+ if (!nextchar) { // end of file
+ throw 'no escaped character';
+ }
+ if (this.quotes.contains(escapedstate) &&
+ (nextchar != this.state) &&
+ (nextchar != escapedstate)) {
+ this.token = this.token + this.state;
+ }
+ this.token = this.token + nextchar;
+ this.state = escapedstate;
+ } else if (this.state == 'a') {
+ if (!nextchar) {
+ this.state = null; // end of file
+ break;
+ } else if (this.whitespace.contains(nextchar)) {
+ this.state = ' ';
+ if (this.token || quoted) {
+ break; // emit current token
+ } else {
+ continue;
+ }
+ } else if (this.commenters.contains(nextchar)) {
+ // TODO
+ } else if (this.quotes.contains(nextchar)) {
+ this.state = nextchar;
+ } else if (this.escape.contains(nextchar)) {
+ escapedstate = 'a';
+ this.state = nextchar;
+ } else if ((!~this.nowordchars.indexOf(nextchar)) ||
+ this.quotes.contains(nextchar) ||
+ this.whitespace_split) {
+ this.token = this.token + nextchar;
+ } else {
+ this.pushback.unshift(nextchar);
+ this.state = ' ';
+ if (this.token) {
+ break; // emit current token
+ } else {
+ continue;
+ }
+ }
+ }
+ }
+ var result = this.token;
+ this.token = '';
+ if (!quoted && result === '') {
+ result = null;
+ }
+ return result;
+ },
+ next: function() {
+ var token = this.get_token();
+ if (token == this.eof) {
+ return null;
+ }
+ return token;
+ }
+ });
+
+ Sao.common.DomainParser = Sao.class_(Object, {
+ OPERATORS: ['!=', '<=', '>=', '=', '!', '<', '>'],
+ init: function(fields, context) {
+ this.fields = {};
+ this.strings = {};
+ this.context = context;
+ this.update_fields(fields);
+ },
+ update_fields: function(fields, prefix, string_prefix) {
+ prefix = prefix || '';
+ string_prefix = string_prefix || '';
+ for (var name in fields) {
+ var field = fields[name];
+ if ((field.searchable || (field.searchable === undefined)) &&
+ (name !== 'rec_name')) {
+ field = jQuery.extend({}, field);
+ var fullname = prefix ? prefix + '.' + name : name;
+ var string = string_prefix ?
+ string_prefix + '.' + field.string : field.string;
+ field.string = string;
+ field.name = fullname;
+ this.fields[fullname] = field;
+ this.strings[field.string.toLowerCase()] = field;
+ var rfields = field.relation_fields;
+ if (rfields) {
+ this.update_fields(rfields, fullname, string);
+ }
+ }
+ }
+ },
+ parse: function(input) {
+ try {
+ var lex = new Sao.common.udlex(input);
+ var tokens = [];
+ while (true) {
+ var token = lex.next();
+ if (token === null) {
+ break;
+ }
+ tokens.push(token);
+ }
+ tokens = this.group_operator(tokens);
+ tokens = this.parenthesize(tokens);
+ tokens = this.group(tokens);
+ tokens = this.operatorize(tokens, 'or');
+ tokens = this.operatorize(tokens, 'and');
+ tokens = this.parse_clause(tokens);
+ return this.simplify(tokens);
+ } catch (e) {
+ if (e == 'no closing quotation') {
+ return this.parse(input + '"');
+ }
+ throw e;
+ }
+ },
+ stringable: function(domain) {
+ var stringable_ = function(clause) {
+ if (!clause) {
+ return true;
+ }
+ var is_array = function(e) {
+ return e instanceof Array;
+ };
+ if ((~['AND', 'OR'].indexOf(clause[0]) ||
+ (is_array(clause[0]))) &&
+ clause.slice(1).every(is_array)) {
+ return this.stringable(clause);
+ }
+ var name = clause[0];
+ var value = clause[2];
+ if (name.endsWith('.rec_name')) {
+ name = name.slice(0, -9);
+ }
+ if (name in this.fields) {
+ var field = this.fields[name];
+ if (~['many2one', 'one2one', 'one2many', 'many2many']
+ .indexOf(field.type)) {
+ var test = function(value) {
+ if (field.type == 'many2one') {
+ if ((typeof value != 'string') &&
+ (value !== null)) {
+ return false;
+ }
+ } else {
+ if (typeof value != 'string') {
+ return false;
+ }
+ }
+ return true;
+ };
+ if (value instanceof Array) {
+ return value.every(test);
+ } else {
+ return test(value);
+ }
+ } else if (field.type == 'multiselection') {
+ return (!value ||
+ jQuery.isEmptyObject(value) ||
+ (value instanceof Array));
+ } else {
+ return true;
+ }
+ } else if (name == 'rec_name') {
+ return true;
+ }
+ return false;
+ }.bind(this);
+ if (!domain) {
+ return true;
+ }
+ if (~['AND', 'OR'].indexOf(domain[0])) {
+ domain = domain.slice(1);
+ }
+ return domain.every(stringable_);
+ },
+ string: function(domain) {
+
+ var string = function(clause) {
+ if (jQuery.isEmptyObject(clause)) {
+ return '';
+ }
+ if ((typeof clause[0] != 'string') ||
+ ~['AND', 'OR'].indexOf(clause[0])) {
+ return '(' + this.string(clause) + ')';
+ }
+ var escaped;
+ var name = clause[0];
+ var operator = clause[1];
+ var value = clause[2];
+ if (name.endsWith('.rec_name')) {
+ name = name.slice(0, -9);
+ }
+ if (!(name in this.fields)) {
+ if (this.is_full_text(value)) {
+ value = value.slice(1, -1);
+ }
+ return this.quote(value);
+ }
+ var field = this.fields[name];
+ var target = null;
+ if (clause.length > 3) {
+ target = clause[3];
+ }
+ if (operator.contains('ilike')) {
+ if (this.is_full_text(value)) {
+ value = value.slice(1, -1);
+ } else if (!this.is_like(value)) {
+ if (operator == 'ilike') {
+ operator = '=';
+ } else {
+ operator = '!';
+ }
+ value = this.unescape(value);
+ }
+ }
+ var def_operator = this.default_operator(field);
+ if (def_operator == operator.trim()) {
+ operator = '';
+ if (~this.OPERATORS.indexOf(value)) {
+ // As the value could be interpreted as an operator
+ // the default operator must be forced
+ operator = '"" ';
+ }
+ } else if ((operator.contains(def_operator) &&
+ (operator.contains('not') ||
+ operator.contains('!')))) {
+ operator = operator.replace(def_operator, '')
+ .replace('not', '!').trim();
+ }
+ if (operator.endsWith('in')) {
+ if (operator == 'not in') {
+ operator = '!';
+ } else {
+ operator = '';
+ }
+ }
+ var formatted_value = this.format_value(field, value, target);
+ if (~this.OPERATORS.indexOf(operator) &&
+ ~['char', 'text', 'selection']
+ .indexOf(field.type) &&
+ (value === '')) {
+ formatted_value = '""';
+ }
+ return (this.quote(field.string) + ': ' +
+ operator + formatted_value);
+ };
+ string = string.bind(this);
+
+ if (jQuery.isEmptyObject(domain)) {
+ return '';
+ }
+ var nary = ' ';
+ if ((domain[0] == 'AND') || (domain[0] == 'OR')) {
+ if (domain[0] == 'OR') {
+ nary = ' or ';
+ }
+ domain = domain.slice(1);
+ }
+ return domain.map(string).join(nary);
+ },
+ completion: function(input) {
+ var results = [];
+ var domain = this.parse(input);
+ var closing = 0;
+ var i, len;
+ for (i=input.length; i>0; i--) {
+ if (input[i] == ')' || input[i] == ' ') {
+ break;
+ }
+ if (input[i] == ')') {
+ closing += 1;
+ }
+ }
+ var endings = this.ending_clause(domain);
+ var ending = endings[0];
+ var deep_ending = endings[1];
+ var deep = deep_ending - closing;
+ var string_domain = this.string(domain);
+
+ if (deep > 0) {
+ string_domain = string_domain.substring(0,
+ string_domain.length - deep);
+ }
+ if (string_domain != input) {
+ results.push(string_domain);
+ }
+
+ var pslice = function(string, depth) {
+ if (depth > 0) {
+ return string.substring(0, string.length - depth);
+ }
+ return string;
+ };
+ var complete, complete_string;
+ if (ending !== null && closing === 0) {
+ var completes = this.complete(ending);
+ for (i=0, len=completes.length; i < len; i++) {
+ complete = completes[i];
+ complete_string = this.string(
+ this.replace_ending_clause(domain, complete));
+ results.push(pslice(complete_string, deep));
+ }
+ }
+ if (input.length > 0) {
+ if (input.substr(input.length - 1, 1) != ' ') {
+ return results;
+ }
+ if (input.length >= 2 ||
+ input.substr(input.length - 2, 1) == ':') {
+ return results;
+ }
+ }
+ var field, operator, value;
+ for (var key in this.strings) {
+ field = this.strings[key];
+ operator = this.default_operator(field);
+ value = '';
+ if ((operator == 'ilike') || (operator == 'not ilike')) {
+ value = this.likify(value);
+ }
+ var new_domain = this.append_ending_clause(domain,
+ [field.name, operator, value], deep);
+ var new_domain_string = this.string(new_domain);
+ results.push(pslice(new_domain_string, deep));
+ }
+ return results;
+ },
+ complete: function(clause) {
+ var results = [];
+ var name, operator, value, target;
+ if (clause.length == 1) {
+ name = clause[0];
+ } else if (clause.length == 3) {
+ name = clause[0];
+ operator = clause[1];
+ value = clause[2];
+ } else {
+ name = clause[0];
+ operator = clause[1];
+ value = clause[2];
+ target = clause[3];
+ if (name.endsWith('.rec_name')) {
+ name = name.substring(0, name.length - 9);
+ }
+ }
+ var escaped;
+ if (name == "rec_name") {
+ if (operator == "ilike") {
+ escaped = value.replace(/%%/g, '__');
+ if (escaped.startsWith('%') || escaped.endsWith('%')) {
+ value = escaped.substring(1, escaped.length - 1);
+ } else if (~escaped.indexOf('%')) {
+ value = value.replace(/%%/g, '%');
+ }
+ operator = null;
+ }
+ name = value;
+ value = '';
+ }
+ if (name === undefined || name === null) {
+ name = '';
+ }
+ var field;
+ if (!(name.toLowerCase() in this.strings) &&
+ !(name in this.fields)) {
+ for (var key in this.strings) {
+ field = this.strings[key];
+ if (field.string.toLowerCase()
+ .startsWith(name.toLowerCase())) {
+ operator = this.default_operator(field);
+ value = '';
+ if (operator == 'ilike') {
+ value = this.likify(value);
+ }
+ results.push([field.name, operator, value]);
+ }
+ }
+ return results;
+ }
+ if (name in this.fields) {
+ field = this.fields[name];
+ } else {
+ field = this.strings[name.toLowerCase()];
+ }
+ if (!operator) {
+ operator = this.default_operator(field);
+ value = '';
+ if ((operator == 'ilike') || (operator == 'not ilike')) {
+ value = this.likify(value);
+ }
+ results.push([field.name, operator, value]);
+ } else {
+ var completes = this.complete_value(field, value);
+ for (var i=0, len=completes.length; i < len; i++) {
+ results.push([field.name, operator, completes[i]]);
+ }
+ }
+ return results;
+ },
+ is_subdomain: function(element) {
+ return (element instanceof Array) && !element.clause;
+ },
+ ending_clause: function(domain, depth) {
+ if (depth === undefined) {
+ depth = 0;
+ }
+ if (domain.length === 0) {
+ return [null, depth];
+ }
+ var last_element = domain[domain.length - 1];
+ if (this.is_subdomain(last_element)) {
+ return this.ending_clause(last_element, depth + 1);
+ }
+ return [last_element, depth];
+ },
+ replace_ending_clause: function(domain, clause) {
+ var results = [];
+ var i, len;
+ for (i = 0, len=domain.length - 1; i < len; i++) {
+ results.push(domain[i]);
+ }
+ if (this.is_subdomain(domain[i])) {
+ results.push(
+ this.replace_ending_clause(domain[i], clause));
+ } else {
+ results.push(clause);
+ }
+ return results;
+ },
+ append_ending_clause: function(domain, clause, depth) {
+ if (domain.length === 0) {
+ return [clause];
+ }
+ var results = domain.slice(0, -1);
+ var last_element = domain[domain.length - 1];
+ if (this.is_subdomain(last_element)) {
+ results.push(this.append_ending_clause(last_element, clause,
+ depth - 1));
+ } else {
+ results.push(last_element);
+ if (depth === 0) {
+ results.push(clause);
+ }
+ }
+ return results;
+ },
+ complete_value: function(field, value) {
+ var complete_boolean = function() {
+ if ((value === null) || (value === undefined)) {
+ return [true, false];
+ } else if (value) {
+ return [false];
+ } else {
+ return [true];
+ }
+ };
+
+ var complete_selection = function() {
+ var results = [];
+ var test_value = value !== null ? value : '';
+ if (value instanceof Array) {
+ test_value = value[value.length - 1] || '';
+ }
+ test_value = test_value.replace(/^%*|%*$/g, '');
+ var i, len, svalue, test;
+ for (i=0, len=field.selection.length; i=', lvalue]),
+ this._clausify([field_name, '<=', rvalue])
+ ]);
+ return;
+ }
+ }
+ if (value instanceof Array) {
+ value = value.map(function(v) {
+ return this.convert_value(field, v, this.context);
+ }.bind(this));
+ if (~['many2one', 'one2many', 'many2many', 'one2one',
+ 'many2many', 'one2one'].indexOf(field.type)) {
+ field_name += '.rec_name';
+ }
+ } else {
+ value = this.convert_value(field, value, this.context);
+ }
+ if (operator.contains('like')) {
+ value = this.likify(value);
+ }
+ if (target) {
+ result.push(this._clausify(
+ [field_name, operator, value, target]));
+ } else {
+ result.push(this._clausify(
+ [field_name, operator, value]));
+ }
+ }
+ }.bind(this));
+ return result;
+ },
+ likify: function(value, escape) {
+ escape = escape || '\\';
+ if (!value) {
+ return '%';
+ }
+ var escaped = value
+ .replace(escape + '%', '')
+ .replace(escape + '_', '');
+ if (escaped.contains('%') || escaped.contains('_')) {
+ return value;
+ } else {
+ return '%' + value + '%';
+ }
+ },
+ is_full_text: function(value, escape) {
+ escape = escape || '\\';
+ var escaped = value;
+ while (escaped.charAt(0) == '%') {
+ escaped = escaped.substring(1);
+ }
+ while (escaped.charAt(escaped.length - 1) == '%') {
+ escaped = escaped.substring(0, escaped.length - 1);
+ }
+ escaped = escaped
+ .replace(escape + '%', '')
+ .replace(escape + '_', '');
+ if (escaped.contains('%') || escaped.contains('_')) {
+ return false;
+ }
+ return value.startsWith('%') && value.endsWith('%');
+ },
+ is_like: function(value, escape) {
+ escape = escape || '\\';
+ var escaped = value
+ .replace(escape + '%', '')
+ .replace(escape + '_', '');
+ return escaped.contains('%') || escaped.contains('_');
+ },
+ unescape: function(value, escape) {
+ escape = escape || '\\';
+ return value
+ .replace(escape + '%', '%')
+ .replace(escape + '_', '_');
+ },
+ quote: function(value) {
+ if (typeof value != 'string') {
+ return value;
+ }
+ if (value.contains('\\')) {
+ value = value.replace(new RegExp('\\\\', 'g'), '\\\\');
+ }
+ if (value.contains('"')) {
+ value = value.replace(new RegExp('"', 'g'), '\\"');
+ }
+ var tests = [':', ' ', '(', ')'].concat(this.OPERATORS);
+ for (var i = 0; i < tests.length; i++) {
+ var test = tests[i];
+ if (value.contains(test)) {
+ return '"' + value + '"';
+ }
+ }
+ return value;
+ },
+ default_operator: function(field) {
+ if (~['char', 'text', 'many2one', 'many2many', 'one2many',
+ 'reference', 'one2one'].indexOf(field.type)) {
+ return 'ilike';
+ } else if (field.type == 'multiselection') {
+ return 'in';
+ } else {
+ return '=';
+ }
+ },
+ negate_operator: function(operator) {
+ switch (operator) {
+ case 'ilike':
+ return 'not ilike';
+ case '=':
+ return '!=';
+ case 'in':
+ return 'not in';
+ }
+ },
+ time_format: function(field) {
+ return new Sao.PYSON.Decoder({}).decode(field.format);
+ },
+ split_target_value: function(field, value) {
+ var target = null;
+ if (typeof value == 'string') {
+ for (var i = 0; i < field.selection.length; i++) {
+ var selection = field.selection[i];
+ var key = selection[0];
+ var text = selection[1];
+ if (value.toLowerCase().startsWith(
+ text.toLowerCase() + ',')) {
+ target = key;
+ value = value.slice(text.length + 1);
+ break;
+ }
+ }
+ }
+ return [target, value];
+ },
+ convert_value: function(field, value, context) {
+ if (!context) {
+ context = {};
+ }
+ var convert_selection = function() {
+ if (typeof value == 'string') {
+ for (var i = 0; i < field.selection.length; i++) {
+ var selection = field.selection[i];
+ var key = selection[0];
+ var text = selection[1];
+ if (value.toLowerCase() == text.toLowerCase()) {
+ return key;
+ }
+ }
+ }
+ return value;
+ };
+
+ var converts = {
+ 'boolean': function() {
+ if (typeof value == 'string') {
+ return [Sao.i18n.gettext('y'),
+ Sao.i18n.gettext('Yes'),
+ Sao.i18n.gettext('True'),
+ Sao.i18n.gettext('t'),
+ '1'].some(
+ function(test) {
+ return test.toLowerCase().startsWith(
+ value.toLowerCase());
+ });
+ }
+ return null;
+ },
+ 'float': function() {
+ var factor = Number(field.factor || 1);
+ var result = Number(value);
+ if (isNaN(result) || value === '' || value === null) {
+ return null;
+ } else {
+ return result / factor;
+ }
+ },
+ 'integer': function() {
+ var factor = Number(field.factor || 1, 10);
+ var result = parseInt(value, 10);
+ if (isNaN(result)) {
+ return null;
+ } else {
+ return result / factor;
+ }
+ },
+ 'numeric': function() {
+ var factor = Number(field.factor || 1);
+ var result = Number(value);
+ if (isNaN(result.valueOf()) ||
+ value === '' || value === null) {
+ return null;
+ } else {
+ return new Sao.Decimal(result / factor);
+ }
+ },
+ 'selection': convert_selection,
+ 'multiselection': convert_selection,
+ 'reference': convert_selection,
+ 'datetime': function() {
+ var result = Sao.common.parse_datetime(
+ Sao.common.date_format(context.date_format) + ' ' +
+ this.time_format(field), value);
+ return result;
+ }.bind(this),
+ 'date': function() {
+ return Sao.common.parse_date(
+ Sao.common.date_format(context.date_format),
+ value);
+ },
+ 'time': function() {
+ try {
+ return Sao.common.parse_time(this.time_format(field),
+ value);
+ } catch (e) {
+ return null;
+ }
+ }.bind(this),
+ 'timedelta': function() {
+ var converter = null;
+ if (field.converter) {
+ converter = this.context[field.converter];
+ }
+ return Sao.common.timedelta.parse(value, converter);
+ }.bind(this),
+ 'many2one': function() {
+ if (value === '') {
+ return null;
+ } else {
+ return value;
+ }
+ }
+ };
+ var func = converts[field.type];
+ if (func) {
+ return func();
+ } else {
+ return value;
+ }
+ },
+ format_value: function(field, value, target, context) {
+ if (target === undefined) {
+ target = null;
+ }
+ if (!context) {
+ context = {};
+ }
+ var format_float = function() {
+ if (!value && value !== 0 && value !== new Sao.Decimal(0)) {
+ return '';
+ }
+ var digit = 0;
+ var factor = Number(field.factor || 1);
+ var string = String(value * factor);
+ if (string.contains('e')) {
+ var exp = string.split('e')[1];
+ string = string.split('e')[0];
+ digit -= parseInt(exp);
+ }
+ if (string.contains('.')) {
+ digit += string.replace(/0+$/, '').split('.')[1].length;
+ }
+ return (value * factor).toFixed(digit);
+ };
+ var format_selection = function() {
+ if (field.selection instanceof Array) {
+ for (var i = 0; i < field.selection.length; i++) {
+ if (field.selection[i][0] == value) {
+ return field.selection[i][1];
+ }
+ }
+ }
+ return value || '';
+ };
+
+ var format_reference = function() {
+ if (!target) {
+ return format_selection();
+ }
+ for (var i = 0; i < field.selection.length; i++) {
+ if (field.selection[i][0] == target) {
+ target = field.selection[i][1];
+ break;
+ }
+ }
+ return target + ',' + value;
+ };
+
+ var converts = {
+ 'boolean': function() {
+ if (value === false) {
+ return Sao.i18n.gettext('False');
+ } else if (value) {
+ return Sao.i18n.gettext('True');
+ } else {
+ return '';
+ }
+ },
+ 'integer': function() {
+ var factor = Number(field.factor || 1);
+ if (value || value === 0) {
+ return '' + parseInt(parseInt(value, 10) * factor, 10);
+ } else {
+ return '';
+ }
+ },
+ 'float': format_float,
+ 'numeric': format_float,
+ 'selection': format_selection,
+ 'multiselection': format_selection,
+ 'reference': format_reference,
+ 'datetime': function() {
+ if (!value) {
+ return '';
+ }
+ if (value.isDate ||
+ !(value.hour() ||
+ value.minute() ||
+ value.second())) {
+ return Sao.common.format_date(
+ Sao.common.date_format(context.date_format),
+ value);
+ }
+ return Sao.common.format_datetime(
+ Sao.common.date_format(context.date_format) + ' ' +
+ this.time_format(field), value);
+ }.bind(this),
+ 'date': function() {
+ return Sao.common.format_date(
+ Sao.common.date_format(context.date_format),
+ value);
+ },
+ 'time': function() {
+ if (!value) {
+ return '';
+ }
+ return Sao.common.format_time(
+ this.time_format(field),
+ value);
+ }.bind(this),
+ 'timedelta': function() {
+ if (!value || !value.valueOf()) {
+ return '';
+ }
+ var converter = null;
+ if (field.converter) {
+ converter = this.context[field.converter];
+ }
+ return Sao.common.timedelta.format(value, converter);
+ }.bind(this),
+ 'many2one': function() {
+ if (value === null) {
+ return '';
+ } else {
+ return value;
+ }
+ }
+ };
+ if (value instanceof Array) {
+ return value.map(function(v) {
+ return this.format_value(field, v);
+ }.bind(this)).join(';');
+ } else {
+ var func = converts[field.type];
+ if (func) {
+ return this.quote(func(value));
+ } else if (value === null) {
+ return '';
+ } else {
+ return this.quote(value);
+ }
+ }
+ },
+ simplify: function(value) {
+ if (this.is_subdomain(value)) {
+ if ((value.length == 1) && this.is_subdomain(value[0])) {
+ return this.simplify(value[0]);
+ } else if ((value.length == 2) &&
+ ((value[0] == 'AND') || (value[0] == 'OR')) &&
+ this.is_subdomain(value[1])) {
+ return this.simplify(value[1]);
+ } else if ((value.length == 3) &&
+ ((value[0] == 'AND') || (value[0] == 'OR')) &&
+ this.is_subdomain(value[1]) &&
+ (value[0] == value[1][0])) {
+ value = this.simplify(value[1]).concat([value[2]]);
+ }
+ return value.map(this.simplify.bind(this));
+ }
+ return value;
+ }
+ });
+
+ Sao.common.DomainInversion = Sao.class_(Object, {
+ and: function(a, b) {return a && b;},
+ or: function(a, b) {return a || b;},
+ OPERATORS: {
+ '=': function(a, b) {
+ return Sao.common.DomainInversion.equals(a, b);
+ },
+ '>': function(a, b) {return (a > b);},
+ '<': function(a, b) {return (a < b);},
+ '<=': function(a, b) {return (a <= b);},
+ '>=': function(a, b) {return (a >= b);},
+ '!=': function(a, b) {
+ return !Sao.common.DomainInversion.equals(a, b);
+ },
+ 'in': function(a, b) {
+ return Sao.common.DomainInversion.in_(a, b);
+ },
+ 'not in': function(a, b) {
+ return !Sao.common.DomainInversion.in_(a, b);
+ },
+ 'like': function(a, b) {
+ return Sao.common.DomainInversion.sql_like(a, b, false);
+ },
+ 'ilike': function(a, b) {
+ return Sao.common.DomainInversion.sql_like(a, b, true);
+ },
+ 'not like': function(a, b) {
+ return !Sao.common.DomainInversion.sql_like(a, b, false);
+ },
+ 'not ilike': function(a, b) {
+ return !Sao.common.DomainInversion.sql_like(a, b, true);
+ },
+ // Those operators are not supported (yet ?)
+ 'child_of': function() {return true;},
+ 'not child_of': function() {return true;}
+ },
+ locale_part: function(expression, field_name, locale_name) {
+ if (locale_name === undefined) {
+ locale_name = 'id';
+ }
+ if (expression === field_name) {
+ return locale_name;
+ }
+ if (expression.contains('.')) {
+ return expression.split('.').slice(1).join('.');
+ }
+ return expression;
+ },
+ is_leaf: function(expression) {
+ return ((expression instanceof Array) &&
+ (expression.length > 2) &&
+ (typeof expression[1] == 'string'));
+ },
+ constrained_leaf: function(part, boolop) {
+ if (boolop === undefined) {
+ boolop = this.and;
+ }
+ var field = part[0];
+ var operand = part[1];
+ var value = part[2];
+ if ((operand === '=') & (boolop === this.and)) {
+ // We should consider that other domain inversion will set a
+ // correct value to this field
+ return true;
+ }
+ return false;
+ },
+ eval_leaf: function(part, context, boolop) {
+ if (boolop === undefined) {
+ boolop = this.and;
+ }
+ var field = part[0];
+ var operand = part[1];
+ var value = part[2];
+ if (field.contains('.')) {
+ // In the case where the leaf concerns a m2o then having a
+ // value in the evaluation context is deemed suffisant
+ return Boolean(context[field.split('.')[0]]);
+ }
+ var context_field = context[field];
+ if (!~['=', '!='].indexOf(operand) &&
+ ((context_field === null) ||
+ (context_field === undefined) ||
+ (value === null) ||
+ (value === undefined)) &&
+ !(~['in', 'not in'].indexOf(operand) &&
+ ((context_field === null) ||
+ (context_field === undefined)) &&
+ ((value instanceof Array) && ~value.indexOf(null)))) {
+ return;
+ }
+ if ((context_field && context_field._isAMomentObject) && !value) {
+ if (context_field.isDateTime) {
+ value = Sao.DateTime.min;
+ } else {
+ value = Sao.Date.min;
+ }
+ }
+ if ((value && value._isAMomentObject) && !context_field) {
+ if (value.isDateTime) {
+ context_field = Sao.DateTime.min;
+ } else {
+ context_field = Sao.Date.min;
+ }
+ }
+ if ((context_field instanceof Array) & (value === null)) {
+ value = [];
+ }
+ if ((typeof context_field == 'string') &&
+ (value instanceof Array) && value.length == 2) {
+ value = value.join(',');
+ } else if ((context_field instanceof Array) &&
+ (typeof value == 'string') && context_field.length == 2) {
+ context_field = context_field.join(',');
+ }
+ if (~['=', '!='].indexOf(operand) &&
+ context_field instanceof Array &&
+ typeof value == 'number') {
+ operand = {
+ '=': 'in',
+ '!=': 'not in'
+ }[operand];
+ }
+ if (operand in this.OPERATORS) {
+ return this.OPERATORS[operand](context_field, value);
+ } else {
+ return true;
+ }
+ },
+ inverse_leaf: function(domain) {
+ if (~['AND', 'OR'].indexOf(domain)) {
+ return domain;
+ } else if (this.is_leaf(domain)) {
+ if (domain[1].contains('child_of') && !domain[0].contains('.')) {
+ if (domain.length == 3) {
+ return domain;
+ } else {
+ return [domain[3]].concat(domain.slice(1));
+ }
+ }
+ return domain;
+ } else {
+ return domain.map(this.inverse_leaf.bind(this));
+ }
+ },
+ filter_leaf: function(domain, field, model) {
+ if (~['AND', 'OR'].indexOf(domain)) {
+ return domain;
+ } else if (this.is_leaf(domain)) {
+ if (domain[0].startsWith(field) && (domain.length > 3)) {
+ if (domain[3] !== model) {
+ return ['id', '=', null];
+ }
+ }
+ return domain;
+ } else {
+ return domain.map(function(d) {
+ return this.filter_leaf(d, field, model);
+ }.bind(this));
+ }
+ },
+ eval_domain: function(domain, context, boolop) {
+ if (boolop === undefined) {
+ boolop = this.and;
+ }
+ if (this.is_leaf(domain)) {
+ return this.eval_leaf(domain, context, boolop);
+ } else if (jQuery.isEmptyObject(domain) && boolop == this.and) {
+ return true;
+ } else if (jQuery.isEmptyObject(domain) && boolop == this.or) {
+ return false;
+ } else if (domain[0] == 'AND') {
+ return this.eval_domain(domain.slice(1), context);
+ } else if (domain[0] == 'OR') {
+ return this.eval_domain(domain.slice(1), context, this.or);
+ } else {
+ return boolop(Boolean(this.eval_domain(domain[0], context)),
+ Boolean(this.eval_domain(domain.slice(1), context, boolop))
+ );
+ }
+ },
+ localize_domain: function(domain, field_name, strip_target) {
+ if (~['AND', 'OR', true, false].indexOf(domain)) {
+ return domain;
+ } else if (this.is_leaf(domain)) {
+ if (domain[1].contains('child_of')) {
+ if (domain[0].split('.').length > 1) {
+ var target = domain[0].split('.').slice(1).join('.');
+ return [target].concat(domain.slice(1));
+ }
+ if (domain.length == 3) {
+ return domain;
+ } else {
+ return [domain[3]].concat(domain.slice(1, -1));
+ }
+ }
+ var local_name = 'id';
+ if (typeof domain[2] == 'string') {
+ local_name = 'rec_name';
+ }
+ var n = strip_target ? 3 : 4;
+ return [this.locale_part(domain[0], field_name, local_name)]
+ .concat(domain.slice(1, n)).concat(domain.slice(4));
+ } else {
+ return domain.map(function(e) {
+ return this.localize_domain(e, field_name, strip_target);
+ }.bind(this));
+ }
+ },
+ prepare_reference_domain: function(domain, reference) {
+
+ var value2reference = function(value) {
+ var model = null;
+ var ref_id = null;
+ if ((typeof(value) == 'string') && value.contains(',')) {
+ var split = value.split(',');
+ var result = split.splice(0, 1);
+ result.push(split.join(','));
+ model = result[0];
+ ref_id = result[1];
+ if (ref_id != '%') {
+ ref_id = parseInt(ref_id, 10);
+ if (isNaN(ref_id)) {
+ model = null;
+ ref_id = value;
+ }
+ }
+ } else if ((value instanceof Array) &&
+ (value.length == 2) &&
+ (typeof(value[0]) == 'string') &&
+ ((typeof(value[1]) == 'number') ||
+ (value[1] == '%'))) {
+ model = value[0];
+ ref_id = value[1];
+ } else {
+ ref_id = value;
+ }
+ return [model, ref_id];
+ };
+
+ if (~['AND', 'OR'].indexOf(domain)) {
+ return domain;
+ } else if (this.is_leaf(domain)) {
+ if (domain[0] == reference) {
+ var model, ref_id, splitted;
+ if ((domain[1] == '=') || (domain[1] == '!=')) {
+ splitted = value2reference(domain[2]);
+ model = splitted[0];
+ ref_id = splitted[1];
+ if (model) {
+ if (ref_id == '%') {
+ if (domain[1] == '=') {
+ return [
+ reference + '.id', '!=', null, model];
+ } else {
+ return [reference, 'not like', domain[2]];
+ }
+ }
+ return [
+ reference + '.id', domain[1], ref_id, model];
+ }
+ } else if ((domain[1] == 'in') || (domain[1] == 'not in')) {
+ var model_values = {};
+ var break_p = false;
+ for (var i=0; i < domain[2].length; i++) {
+ splitted = value2reference(domain[2][i]);
+ model = splitted[0];
+ ref_id = splitted[1];
+ if (!model) {
+ break_p = true;
+ break;
+ }
+ if (!(model in model_values)) {
+ model_values[model] = [];
+ }
+ model_values[model].push(ref_id);
+ }
+
+ if (!break_p) {
+ var ref_ids;
+ var new_domain;
+ if (domain[1] == 'in') {
+ new_domain = ['OR'];
+ } else {
+ new_domain = ['AND'];
+ }
+ for (model in model_values) {
+ ref_ids = model_values[model];
+ if (~ref_ids.indexOf('%')) {
+ if (domain[1] == 'in') {
+ new_domain.push(
+ [reference + '.id', '!=', null,
+ model]);
+ } else {
+ new_domain.push(
+ [reference, 'not like',
+ model + ',%']);
+ }
+ } else {
+ new_domain.push(
+ [reference + '.id', domain[1],
+ ref_ids.map(Number), model]);
+ }
+ }
+ return new_domain;
+ }
+ }
+ return [];
+ }
+ return domain;
+ } else {
+ return domain.map(function(d) {
+ return this.prepare_reference_domain(d, reference);
+ }.bind(this));
+ }
+ },
+ extract_reference_models: function(domain, field_name) {
+ if (~['AND', 'OR'].indexOf(domain)) {
+ return [];
+ } else if (this.is_leaf(domain)) {
+ var local_part = domain[0].split('.', 1)[0];
+ if ((local_part == field_name) &&
+ (domain.length > 3)) {
+ return [domain[3]];
+ }
+ return [];
+ } else {
+ var models = [];
+ domain.map(function(d) {
+ var new_models = this.extract_reference_models(
+ d, field_name);
+ for (var i=0, len=new_models.length; i < len; i++) {
+ var model = new_models[i];
+ if (!~models.indexOf(model)) {
+ models.push(model);
+ }
+ }
+ }.bind(this));
+ return models;
+ }
+ },
+ simplify: function(domain) {
+ if (this.is_leaf(domain)) {
+ return domain;
+ } else if (~['OR', 'AND'].indexOf(domain)) {
+ return domain;
+ } else if ((domain instanceof Array) && (domain.length == 1) &&
+ (~['OR', 'AND'].indexOf(domain[0]))) {
+ return [];
+ } else if ((domain instanceof Array) && (domain.length == 1) &&
+ (!this.is_leaf(domain[0]))) {
+ return this.simplify(domain[0]);
+ } else if ((domain instanceof Array) && (domain.length == 2) &&
+ ~['AND', 'OR'].indexOf(domain[0])) {
+ return [this.simplify(domain[1])];
+ } else {
+ return domain.map(this.simplify.bind(this));
+ }
+ },
+ merge: function(domain, domoperator) {
+ if (jQuery.isEmptyObject(domain) ||
+ ~['AND', 'OR'].indexOf(domain)) {
+ return [];
+ }
+ var domain_type = domain[0] == 'OR' ? 'OR' : 'AND';
+ if (this.is_leaf(domain)) {
+ return [domain];
+ } else if (domoperator === undefined) {
+ return [domain_type].concat([].concat.apply([],
+ domain.map(function(e) {
+ return this.merge(e, domain_type);
+ }.bind(this))));
+ } else if (domain_type == domoperator) {
+ return [].concat.apply([], domain.map(function(e) {
+ return this.merge(e, domain_type);
+ }.bind(this)));
+ } else {
+ // without setting the domoperator
+ return [this.merge(domain)];
+ }
+ },
+ concat: function(domains, domoperator) {
+ var result = [];
+ if (domoperator) {
+ result.push(domoperator);
+ }
+ domains.forEach(function append(domain) {
+ if (!jQuery.isEmptyObject(domain)) {
+ result.push(domain);
+ }
+ });
+ return this.simplify(this.merge(result));
+ },
+ unique_value: function(domain) {
+ if ((domain instanceof Array) &&
+ (domain.length == 1)) {
+ domain = domain[0];
+ var name = domain[0];
+ var value = domain[2];
+ var count = 0;
+ if (domain.length == 4 && name.endsWith('.id')) {
+ count = 1;
+ var model = domain[3];
+ value = [model, value];
+ }
+ if ((name.split('.').length - 1) == count &&
+ (domain[1] == '=')) {
+ return [true, domain[1], value];
+ }
+ }
+ return [false, null, null];
+ },
+ parse: function(domain) {
+ var And = Sao.common.DomainInversion.And;
+ var Or = Sao.common.DomainInversion.Or;
+ if (this.is_leaf(domain)) {
+ return domain;
+ } else if (jQuery.isEmptyObject(domain)) {
+ return new And([]);
+ } else if (domain[0] === 'OR') {
+ return new Or(domain.slice(1));
+ } else {
+ var begin = 0;
+ if (domain[0] === 'AND') {
+ begin = 1;
+ }
+ return new And(domain.slice(begin));
+ }
+ },
+ domain_inversion: function(domain, symbol, context) {
+ if (context === undefined) {
+ context = {};
+ }
+ var expression = this.parse(domain);
+ if (!~expression.variables.indexOf(symbol)) {
+ return true;
+ }
+ return expression.inverse(symbol, context);
+ }
+ });
+ Sao.common.DomainInversion.equals = function(a, b) {
+ if ((a instanceof Array) && (b instanceof Array)) {
+ return Sao.common.compare(a, b);
+ } else if (moment.isMoment(a) && moment.isMoment(b)) {
+ return ((a.isDate == b.isDate) &&
+ (a.isDateTime == b.isDateTime) &&
+ (a.valueOf() == b.valueOf()));
+ } else if ((a instanceof Number) || (b instanceof Number)) {
+ return (Number(a) === Number(b));
+ } else {
+ return (a === b);
+ }
+ };
+ Sao.common.DomainInversion.in_ = function(a, b) {
+ if (a instanceof Array) {
+ if (b instanceof Array) {
+ for (var i = 0, len = a.length; i < len; i++) {
+ if (~b.indexOf(a[i])) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return Boolean(~a.indexOf(b));
+ }
+ } else {
+ return Boolean(~b.indexOf(a));
+ }
+ };
+ Sao.common.DomainInversion.sql_like = function(value, pattern, ignore_case)
+ {
+ var escape = false;
+ var chars = [];
+ var splitted = pattern.split(/(.|\\)/);
+ var char;
+ for (var i=1, len=splitted.length; i < len; i = i+2) {
+ char = splitted[i];
+ if (escape) {
+ if ((char == '%') || (char == '_')) {
+ chars.push(char);
+ } else {
+ chars.push('\\', char);
+ }
+ escape = false;
+ } else if (char == '\\') {
+ escape = true;
+ } else if (char == '_') {
+ chars.push('.');
+ } else if (char == '%') {
+ chars.push('.*');
+ } else {
+ chars.push(char);
+ }
+ }
+
+ if (!pattern.startsWith('%')) {
+ chars.splice(0, 0, '^');
+ }
+ if (!pattern.endsWith('%')) {
+ chars.push('$');
+ }
+
+ var flags = ignore_case ? 'i' : '';
+ var regexp = new RegExp(chars.join(''), flags);
+ return regexp.test(value);
+ };
+ Sao.common.DomainInversion.And = Sao.class_(Object, {
+ init: function(expressions) {
+ this.domain_inversion = new Sao.common.DomainInversion();
+ this.branches = expressions.map(this.domain_inversion.parse.bind(
+ this.domain_inversion));
+ this.variables = [];
+ for (var i = 0, len = this.branches.length; i < len; i++) {
+ var expression = this.branches[i];
+ if (this.domain_inversion.is_leaf(expression)) {
+ this.variables.push(this.base(expression[0]));
+ } else if (expression instanceof
+ Sao.common.DomainInversion.And) {
+ this.variables = this.variables.concat(
+ expression.variables);
+ }
+ }
+ },
+ base: function(expression) {
+ if (!expression.contains('.')) {
+ return expression;
+ } else {
+ return expression.split('.')[0];
+ }
+ },
+ inverse: function(symbol, context) {
+ var DomainInversion = Sao.common.DomainInversion;
+ var result = [];
+ for (var i = 0, len = this.branches.length; i < len; i++) {
+ var part = this.branches[i];
+ if (part instanceof DomainInversion.And) {
+ var part_inversion = part.inverse(symbol, context);
+ var evaluated = typeof part_inversion == 'boolean';
+ if (!~part.variables.indexOf(symbol)) {
+ continue;
+ }
+ if (!evaluated) {
+ result.push(part_inversion);
+ } else if (part_inversion) {
+ continue;
+ } else {
+ return false;
+ }
+ } else if (this.domain_inversion.is_leaf(part) &&
+ (this.base(part[0]) === symbol)) {
+ result.push(part);
+ } else {
+ var field = part[0];
+ if ((!(field in context)) ||
+ ((field in context) &&
+ (this.domain_inversion.eval_leaf(
+ part, context, this.domain_inversion.and) ||
+ this.domain_inversion.constrained_leaf(
+ part, this.domain_inversion.and)))) {
+ result.push(true);
+ } else {
+ return false;
+ }
+ }
+ }
+ result = result.filter(function(e) {
+ return e !== true;
+ });
+ if (jQuery.isEmptyObject(result)) {
+ return true;
+ } else {
+ return this.domain_inversion.simplify(result);
+ }
+ }
+ });
+ Sao.common.DomainInversion.Or = Sao.class_(Sao.common.DomainInversion.And, {
+ inverse: function(symbol, context) {
+ var DomainInversion = Sao.common.DomainInversion;
+ var result = [];
+ if (!jQuery.isEmptyObject(this.variables.filter(function(e) {
+ return (!(e in context)) && (e != symbol);
+ }))) {
+ // In this case we don't know enough about this OR part, we
+ // consider it to be True (because people will have the
+ // constraint on this part later).
+ return true;
+ }
+ for (var i = 0, len = this.branches.length; i < len; i++) {
+ var part = this.branches[i];
+ if (part instanceof DomainInversion.And) {
+ var part_inversion = part.inverse(symbol, context);
+ var evaluated = typeof part_inversion == 'boolean';
+ if (!~part.variables.indexOf(symbol)) {
+ if (evaluated && part_inversion) {
+ return true;
+ }
+ continue;
+ }
+ if (!evaluated) {
+ result.push(part_inversion);
+ } else if (part_inversion) {
+ return true;
+ } else {
+ continue;
+ }
+ } else if (this.domain_inversion.is_leaf(part) &&
+ (this.base(part[0]) == symbol)) {
+ result.push(part);
+ } else {
+ var field = part[0];
+ field = this.base(field);
+ if ((field in context) &&
+ (this.domain_inversion.eval_leaf(
+ part, context, this.domain_inversion.or)) ||
+ this.domain_inversion.constrained_leaf(
+ part, this.domain_inversion.or)) {
+ return true;
+ } else if ((field in context) &&
+ !this.domain_inversion.eval_leaf(part, context,
+ this.domain_inversion.or)) {
+ result.push(false);
+ }
+ }
+ }
+ result = result.filter(function(e) {
+ return e !== false;
+ });
+ if (jQuery.isEmptyObject(result)) {
+ return false;
+ } else {
+ return this.domain_inversion.simplify(['OR'].concat(result));
+ }
+ }
+ });
+
+ Sao.common.mimetypes = {
+ 'csv': 'text/csv',
+ 'doc': 'application/msword',
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'gif': 'image/gif',
+ 'html': 'text/html',
+ 'jpeg': 'image/jpeg',
+ 'jpg': 'image/jpeg',
+ 'mpeg': 'video/mpeg',
+ 'mpg': 'video/mpeg',
+ 'ods': 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odt': 'application/vnd.oasis.opendocument.text',
+ 'ogg': 'audio/ogg',
+ 'pdf': 'application/pdf',
+ 'png': 'image/png',
+ 'svg': 'image/svg+xml',
+ 'text': 'text/plain',
+ 'tif': 'image/tif',
+ 'tiff': 'image/tif',
+ 'txt': 'text/plain',
+ 'webp': 'image/webp',
+ 'xls': 'application/vnd.ms-excel',
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml': 'application/xml',
+ 'xpm': 'image/x-xpixmap',
+ };
+
+ Sao.common.guess_mimetype = function(filename) {
+ for (var ext in Sao.common.mimetypes) {
+ var re = new RegExp('.*\.' + ext + '$', 'i');
+ if (re.test(filename)) {
+ return Sao.common.mimetypes[ext];
+ }
+ }
+ return 'application/octet-binary';
+ };
+
+ Sao.common.LOCAL_ICONS = [
+ 'tryton-add',
+ 'tryton-archive',
+ 'tryton-attach',
+ 'tryton-back',
+ 'tryton-bookmark-border',
+ 'tryton-bookmark',
+ 'tryton-bookmarks',
+ 'tryton-cancel',
+ 'tryton-clear',
+ 'tryton-close',
+ 'tryton-copy',
+ 'tryton-create',
+ 'tryton-date',
+ 'tryton-delete',
+ 'tryton-email',
+ 'tryton-error',
+ 'tryton-exit',
+ 'tryton-export',
+ 'tryton-filter',
+ 'tryton-format-align-center',
+ 'tryton-format-align-justify',
+ 'tryton-format-align-left',
+ 'tryton-format-align-right',
+ 'tryton-format-bold',
+ 'tryton-format-color-text',
+ 'tryton-format-italic',
+ 'tryton-format-underline',
+ 'tryton-forward',
+ 'tryton-history',
+ 'tryton-import',
+ 'tryton-info',
+ 'tryton-launch',
+ 'tryton-link',
+ 'tryton-log',
+ 'tryton-menu',
+ 'tryton-note',
+ 'tryton-ok',
+ 'tryton-open',
+ 'tryton-print',
+ 'tryton-public',
+ 'tryton-question',
+ 'tryton-refresh',
+ 'tryton-remove',
+ 'tryton-save',
+ 'tryton-search',
+ 'tryton-send',
+ 'tryton-star-border',
+ 'tryton-star',
+ 'tryton-switch',
+ 'tryton-translate',
+ 'tryton-unarchive',
+ 'tryton-undo',
+ 'tryton-warning',
+ ];
+
+ Sao.common.IconFactory = Sao.class_(Object, {
+ batchnum: 10,
+ name2id: {},
+ loaded_icons: {},
+ tryton_icons: [],
+ register_prm: jQuery.when(),
+ load_icons: function(refresh) {
+ refresh = refresh || false;
+ if (!refresh) {
+ for (var icon_name in this.load_icons) {
+ if (!this.load_icons.hasOwnProperty(icon_name)) {
+ continue;
+ }
+ window.URL.revokeObjectURL(this.load_icons[icon_name]);
+ }
+ }
+
+ var icon_model = new Sao.Model('ir.ui.icon');
+ return icon_model.execute('list_icons', [], {})
+ .then(function(icons) {
+ if (!refresh) {
+ this.name2id = {};
+ this.loaded_icons = {};
+ }
+ this.tryton_icons = [];
+
+ var icon_id, icon_name;
+ for (var i=0, len=icons.length; i < len; i++) {
+ icon_id = icons[i][0];
+ icon_name = icons[i][1];
+ if (refresh && (icon_name in this.loaded_icons)) {
+ continue;
+ }
+ this.tryton_icons.push([icon_id, icon_name]);
+ this.name2id[icon_name] = icon_id;
+ }
+ }.bind(this));
+ },
+ register_icon: function(icon_name) {
+ if (!icon_name) {
+ return jQuery.when();
+ } else if ((icon_name in this.loaded_icons) ||
+ ~Sao.common.LOCAL_ICONS.indexOf(icon_name)) {
+ return jQuery.when();
+ }
+ if (this.register_prm.state() == 'pending') {
+ return this.register_prm.then(function() {
+ return this.register_icon(icon_name);
+ }.bind(this));
+ }
+ var loaded_prm;
+ if (!(icon_name in this.name2id)) {
+ loaded_prm = this.load_icons(true);
+ } else {
+ loaded_prm = jQuery.when();
+ }
+
+ var icon_model = new Sao.Model('ir.ui.icon');
+ this.register_prm = loaded_prm.then(function () {
+ var find_array = function(array) {
+ var idx, l;
+ for (idx=0, l=this.tryton_icons.length; idx < l; idx++) {
+ var icon = this.tryton_icons[idx];
+ if (Sao.common.compare(icon, array)) {
+ break;
+ }
+ }
+ return idx;
+ }.bind(this);
+ var idx = find_array([this.name2id[icon_name], icon_name]);
+ var from = Math.round(idx - this.batchnum / 2);
+ from = (from < 0) ? 0 : from;
+ var to = Math.round(idx + this.batchnum / 2);
+ var ids = [];
+ this.tryton_icons.slice(from, to).forEach(function(e) {
+ ids.push(e[0]);
+ });
+
+ var read_prm = icon_model.execute('read',
+ [ids, ['name', 'icon']], {});
+ return read_prm.then(function(icons) {
+ icons.forEach(function(icon) {
+ var img_url = this._convert(icon.icon);
+ this.loaded_icons[icon.name] = img_url;
+ delete this.name2id[icon.name];
+ this.tryton_icons.splice(
+ find_array([icon.id, icon.name]), 1);
+ }.bind(this));
+ }.bind(this));
+ }.bind(this));
+ return this.register_prm;
+ },
+ _convert: function(data) {
+ var xml = jQuery.parseXML(data);
+ jQuery(xml).find('svg').attr('fill', Sao.config.icon_colors[0]);
+ data = new XMLSerializer().serializeToString(xml);
+ var blob = new Blob([data],
+ {type: 'image/svg+xml'});
+ return window.URL.createObjectURL(blob);
+ },
+ get_icon_url: function(icon_name) {
+ if (!icon_name) {
+ return jQuery.when('');
+ }
+ return this.register_icon(icon_name).then(function() {
+ if (icon_name in this.loaded_icons) {
+ return this.loaded_icons[icon_name];
+ } else {
+ return jQuery.get('images/' + icon_name + '.svg', null, null, 'text')
+ .then(function(icon) {
+ var img_url = this._convert(icon);
+ this.loaded_icons[icon_name] = img_url;
+ return img_url;
+ }.bind(this));
+ }
+ }.bind(this));
+ },
+ get_icon_img: function(icon_name, attrs) {
+ attrs = attrs || {};
+ if (!attrs['class']) {
+ attrs['class'] = 'icon';
+ }
+ var img = jQuery('', attrs);
+ if (icon_name) {
+ this.get_icon_url(icon_name).then(function(url) {
+ img.attr('src', url);
+ });
+ }
+ return img;
+ },
+ });
+
+ Sao.common.ICONFACTORY = new Sao.common.IconFactory();
+
+ Sao.common.UniqueDialog = Sao.class_(Object, {
+ init: function() {
+ this.running = false;
+ },
+ build_dialog: function() {
+ var dialog = new Sao.Dialog('', this.class_, undefined, false);
+ return dialog;
+ },
+ run: function() {
+ if (this.running) {
+ return jQuery.when();
+ }
+ var args = Array.prototype.slice.call(arguments);
+ var prm = jQuery.Deferred();
+ args.push(prm);
+ var dialog = this.build_dialog.apply(this, args);
+ dialog.content.submit(function(evt) {
+ dialog.footer.find('button.btn-primary').first().click();
+ evt.preventDefault();
+ }.bind(this));
+ this.running = true;
+ dialog.modal.modal('show');
+ dialog.modal.on('shown.bs.modal', function() {
+ dialog.modal.find('input,select')
+ .filter(':visible').first().focus();
+ });
+ dialog.modal.on('keydown', function(e) {
+ if (e.which == Sao.common.ESC_KEYCODE) {
+ this.close(dialog);
+ prm.reject();
+ }
+ }.bind(this));
+ return prm;
+ },
+ close: function(dialog) {
+ dialog.modal.on('hidden.bs.modal', function(event) {
+ jQuery(this).remove();
+ });
+ dialog.modal.modal('hide');
+ this.running = false;
+ }
+ });
+
+ Sao.common.MessageDialog = Sao.class_(Sao.common.UniqueDialog, {
+ class_: 'message-dialog',
+ build_dialog: function(message, icon, prm) {
+ var dialog = Sao.common.MessageDialog._super.build_dialog.call(
+ this);
+ dialog.header.remove();
+ dialog.body.append(jQuery('', {
+ 'class': 'alert alert-info',
+ role: 'alert'
+ }).append(Sao.common.ICONFACTORY.get_icon_img(icon, {
+ 'aria-hidden': true,
+ })).append(jQuery('', {
+ 'class': 'sr-only'
+ }).text(Sao.i18n.gettext('Message: '))
+ ).append(jQuery('')
+ .text(message)
+ .css('white-space', 'pre-wrap')));
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('OK')).click(function() {
+ this.close(dialog);
+ prm.resolve('ok');
+ }.bind(this)).appendTo(dialog.footer);
+ return dialog;
+ },
+ run: function(message, icon) {
+ return Sao.common.MessageDialog._super.run.call(
+ this, message, icon || 'tryton-info');
+ }
+ });
+ Sao.common.message = new Sao.common.MessageDialog();
+
+ Sao.common.WarningDialog = Sao.class_(Sao.common.UniqueDialog, {
+ class_: 'warning-dialog',
+ build_dialog: function(message, title, prm) {
+ var dialog = Sao.common.WarningDialog._super.build_dialog.call(
+ this);
+ var content = jQuery('', {
+ 'class': 'alert alert-warning',
+ role: 'alert'
+ }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-warning', {
+ 'aria-hidden': true,
+ })).append(jQuery('', {
+ 'class': 'sr-only'
+ }).text(Sao.i18n.gettext('Warning: '))
+ ).append(jQuery('')
+ .text(title)
+ .css('white-space', 'pre-wrap'));
+ if (message) {
+ content.append(jQuery('')
+ .text(message)
+ .css('white-space', 'pre-wrap'));
+ }
+ dialog.body.append(content);
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('OK')).click(function() {
+ this.close(dialog);
+ prm.resolve('ok');
+ }.bind(this)).appendTo(dialog.footer);
+ return dialog;
+ }
+ });
+ Sao.common.warning = new Sao.common.WarningDialog();
+
+ Sao.common.UserWarningDialog = Sao.class_(Sao.common.WarningDialog, {
+ class_: 'user-warning-dialog',
+ build_dialog: function(message, title, prm) {
+ var dialog = Sao.common.UserWarningDialog._super.build_dialog.call(
+ this, message, title, prm);
+ var always = jQuery('', {
+ 'type': 'checkbox'
+ });
+ dialog.body.append(jQuery('', {
+ 'class': 'checkbox',
+ }).append(jQuery('')
+ .text(Sao.i18n.gettext("Always ignore this warning."))
+ .prepend(always))
+ );
+ dialog.body.append(jQuery('')
+ .text(Sao.i18n.gettext('Do you want to proceed?')));
+ dialog.footer.empty();
+ jQuery('', {
+ 'class': 'btn btn-link',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('No')).click(function() {
+ this.close(dialog);
+ prm.reject();
+ }.bind(this)).appendTo(dialog.footer);
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('Yes')).click(function() {
+ this.close(dialog);
+ if (always.prop('checked')) {
+ prm.resolve('always');
+ }
+ prm.resolve('ok');
+ }.bind(this)).appendTo(dialog.footer);
+ return dialog;
+ }
+ });
+ Sao.common.userwarning = new Sao.common.UserWarningDialog();
+
+ Sao.common.ConfirmationDialog = Sao.class_(Sao.common.UniqueDialog, {
+ class_: 'confirmation-dialog',
+ build_dialog: function(message) {
+ var dialog = Sao.common.ConfirmationDialog._super.build_dialog.call(
+ this);
+ dialog.header.remove();
+ dialog.body.append(jQuery('', {
+ 'class': 'alert alert-info',
+ role: 'alert'
+ }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-info', {
+ 'aria-hidden': true,
+ })).append(jQuery('', {
+ 'class': 'sr-only'
+ }).text(Sao.i18n.gettext('Confirmation: '))
+ ).append(jQuery('')
+ .text(message)
+ .css('white-space', 'pre-wrap')));
+ return dialog;
+ }
+ });
+
+ Sao.common.SurDialog = Sao.class_(Sao.common.ConfirmationDialog, {
+ build_dialog: function(message, prm) {
+ var dialog = Sao.common.SurDialog._super.build_dialog.call(
+ this, message);
+ jQuery('', {
+ 'class': 'btn btn-link',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('Cancel')).click(function() {
+ this.close(dialog);
+ prm.reject();
+ }.bind(this)).appendTo(dialog.footer);
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('OK')).click(function() {
+ this.close(dialog);
+ prm.resolve();
+ }.bind(this)).appendTo(dialog.footer);
+ return dialog;
+ }
+ });
+ Sao.common.sur = new Sao.common.SurDialog();
+
+ Sao.common.Sur3BDialog = Sao.class_(Sao.common.ConfirmationDialog, {
+ build_dialog: function(message, prm) {
+ var dialog = Sao.common.SurDialog._super.build_dialog.call(
+ this, message);
+ jQuery('', {
+ 'class': 'btn btn-link',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('Cancel')).click(function() {
+ this.close(dialog);
+ prm.resolve('cancel');
+ }.bind(this)).appendTo(dialog.footer);
+ jQuery('', {
+ 'class': 'btn btn-default',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('No')).click(function() {
+ this.close(dialog);
+ prm.resolve('ko');
+ }.bind(this)).appendTo(dialog.footer);
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('Yes')).click(function() {
+ this.close(dialog);
+ prm.resolve('ok');
+ }.bind(this)).appendTo(dialog.footer);
+ return dialog;
+ }
+ });
+ Sao.common.sur_3b = new Sao.common.Sur3BDialog();
+
+ Sao.common.AskDialog = Sao.class_(Sao.common.UniqueDialog, {
+ class_: 'ask-dialog',
+ run: function() {
+ var args = Array.prototype.slice.call(arguments);
+ if (args.length == 1) {
+ args.push(true);
+ }
+ return Sao.common.AskDialog._super.run.apply(this, args);
+ },
+ build_dialog: function(question, visibility, prm) {
+ var dialog = Sao.common.AskDialog._super.build_dialog.call(this);
+ dialog.header.remove();
+ var entry = jQuery('', {
+ 'class': 'form-control',
+ 'type': visibility ? 'input' : 'password',
+ 'id': 'ask-dialog-entry'
+ });
+ dialog.body.append(jQuery('', {
+ 'class': 'form-group'
+ }).append(jQuery('', {
+ 'for': 'ask-dialog-entry'
+ }).text(question)).append(entry));
+ jQuery('', {
+ 'class': 'btn btn-link',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('Cancel')).click(function() {
+ this.close(dialog);
+ prm.reject();
+ }.bind(this)).appendTo(dialog.footer);
+ jQuery('', {
+ 'class': 'btn btn-primary',
+ 'type': 'button'
+ }).text(Sao.i18n.gettext('OK')).click(function() {
+ this.close(dialog);
+ prm.resolve(entry.val());
+ }.bind(this)).appendTo(dialog.footer);
+ return dialog;
+ }
+ });
+ Sao.common.ask = new Sao.common.AskDialog();
+
+ Sao.common.ConcurrencyDialog = Sao.class_(Sao.common.UniqueDialog, {
+ class_: 'ask-dialog',
+ build_dialog: function(model, record_id, context, prm) {
+ var dialog = Sao.common.ConcurrencyDialog._super.build_dialog.call(
+ this);
+ dialog.modal.find('.modal-dialog'
+ ).removeClass('modal-sm').addClass('modal-lg');
+ dialog.add_title(Sao.i18n.gettext('Concurrency Exception'));
+ dialog.body.append(jQuery('', {
+ 'class': 'alert alert-info',
+ role: 'alert'
+ }).append(jQuery('')
+ .append(Sao.common.ICONFACTORY.get_icon_img('tryton-info', {
+ 'aria-hidden': true,
+ })).append(jQuery('', {
+ 'class': 'sr-only'
+ }).text(Sao.i18n.gettext('Write Concurrency Warning: '))
+ ).text(Sao.i18n.gettext('This record has been modified ' +
+ 'while you were editing it.')))
+ .append(jQuery('').text(Sao.i18n.gettext('Choose:')))
+ .append(jQuery('