diff options
Diffstat (limited to 'app')
115 files changed, 1456 insertions, 1251 deletions
diff --git a/app/assets/fonts/sBoiv/sboiv.eot b/app/assets/fonts/sBoiv/sboiv.eot Binary files differindex 7bf5449a2..3deb9e00b 100644..100755 --- a/app/assets/fonts/sBoiv/sboiv.eot +++ b/app/assets/fonts/sBoiv/sboiv.eot diff --git a/app/assets/fonts/sBoiv/sboiv.svg b/app/assets/fonts/sBoiv/sboiv.svg index 42e8988cb..497f783bd 100644..100755 --- a/app/assets/fonts/sBoiv/sboiv.svg +++ b/app/assets/fonts/sBoiv/sboiv.svg @@ -17,7 +17,7 @@ <glyph unicode="" glyph-name="transporteur" d="M512 741.069c-81.92 0-147.661-73.318-147.661-163.84 0-93.389 69.018-175.104 147.661-175.104s147.661 81.92 147.661 175.104c0 90.522-65.741 163.84-147.661 163.84zM512 441.651c-56.73 0-108.339 64.717-108.339 135.782s48.538 124.109 108.339 124.109 108.339-55.706 108.339-124.109-51.61-135.782-108.339-135.782zM837.837 960h-651.674c-79.176 0-143.36-64.184-143.36-143.36v0-736.051l6.349-5.734c5.53-5.12 124.723-112.026 376.832-133.939l15.974-1.229 26.010-1.638c15.565 0 31.539-1.229 48.333-1.229s32.563 0 47.923 1.229c11.469 0 22.528 1.229 33.382 2.253h6.554c252.723 20.48 366.797 129.229 371.917 134.349l5.939 5.734v735.027c0.003 0.366 0.005 0.798 0.005 1.231 0 79.176-64.184 143.36-143.36 143.36-0.29 0-0.58-0.001-0.869-0.003h0.045zM586.547-21.197h-9.626l-19.046-1.024c-13.722 0-27.648 0-41.984 0s-28.672 0-42.394 0h-31.13c-36.211 2.821-68.876 7.322-100.909 13.59l5.472-0.892c3.223 88.741 75.939 159.457 165.171 159.457s161.948-70.716 165.163-159.163l0.008-0.293c-25.397-5.041-56.477-9.184-88.060-11.516l-2.666-0.158zM715.981-0.717c-9.114 105.498-97.002 187.68-204.083 187.68s-194.969-82.182-204.030-186.914l-0.053-0.766c-18.227 4.506-32.358 8.806-40.96 11.878 2.048 179.814 46.080 290.611 245.76 290.611s243.507-111.002 245.76-290.816c-9.83-2.253-23.757-6.758-41.574-11.469zM941.261 97.792c-41.585-32.278-89.897-58.509-142.149-76.192l-3.464-1.018c-3.277 162.406-44.646 321.331-284.672 321.331-137.011 0-276.89-38.707-284.467-319.488-55.233 19.071-103.124 44.821-145.948 76.957l1.359-0.976v716.8c0.462 57.303 46.908 103.604 104.221 103.834h651.696c57.264-0.228 103.666-46.414 104.243-103.574v-0.055z" /> <glyph unicode="" glyph-name="trace" d="M220.57 153.702c7.656 11.232 12.225 25.102 12.225 40.038s-4.568 28.806-12.384 40.287l0.16-0.249c-5.259 7.728-11.728 14.197-19.207 19.296l-0.249 0.16c-11.212 7.698-25.077 12.294-40.016 12.294-0.332 0-0.663-0.002-0.994-0.007l0.050 0.001c-0.201 0.002-0.438 0.003-0.676 0.003-5.716 0-11.276-0.673-16.603-1.944l0.485 0.098c-3.413 6.868-10.381 11.506-18.432 11.506-11.331 0-20.517-9.186-20.517-20.517 0-3.28 0.77-6.381 2.139-9.13l-0.054 0.119c0.592-1.011 1.201-1.881 1.872-2.698l-0.028 0.036c-12.243-12.806-19.779-30.202-19.779-49.358 0-39.475 32.001-71.475 71.475-71.475 0.113 0 0.227 0 0.34 0.001h-0.017c0.281-0.004 0.612-0.006 0.944-0.006 14.939 0 28.804 4.596 40.259 12.452l-0.243-0.157c7.635 5.218 14.033 11.617 19.092 19.005l0.159 0.247zM561.357 67.072c-11.212 7.698-25.077 12.294-40.016 12.294-0.332 0-0.663-0.002-0.994-0.007l0.050 0.001c-0.080 0-0.175 0.001-0.27 0.001-31.454 0-58.121-20.493-67.378-48.856l-0.142-0.501c-2.637 2.573-5.971 4.443-9.693 5.298l-0.138 0.027c-1.139 0.221-2.449 0.348-3.789 0.348-11.503 0-20.828-9.325-20.828-20.828 0-10.163 7.279-18.626 16.909-20.459l0.13-0.021h3.891c0.198-0.007 0.43-0.011 0.663-0.011 4.002 0 7.736 1.148 10.891 3.133l-0.085-0.050c4.426-34.809 33.858-61.449 69.513-61.449 0.402 0 0.803 0.003 1.204 0.010l-0.060-0.001c0.281-0.004 0.612-0.006 0.944-0.006 14.939 0 28.804 4.596 40.259 12.452l-0.243-0.157c7.728 5.259 14.197 11.728 19.296 19.207l0.16 0.249c7.656 11.232 12.225 25.102 12.225 40.038s-4.568 28.806-12.384 40.287l0.16-0.249c-5.494 7.696-12.234 14.109-19.987 19.079l-0.288 0.173zM966.246 274.125c-3.801 5.452-8.124 10.165-12.995 14.244l-0.112 0.092c18.132 47.444 28.632 102.317 28.632 159.643 0 64.372-13.241 125.652-37.147 181.264l1.142-2.986c13.419 12.977 21.749 31.143 21.749 51.255 0 39.362-31.909 71.27-71.27 71.27-8.378 0-16.419-1.446-23.886-4.101l0.498 0.155c-70.085 82.874-167.895 140.378-278.969 158.168l-2.631 0.347c-2.074 9.484-5.766 17.855-10.808 25.23l0.158-0.245c-5.218 7.635-11.617 14.033-19.005 19.092l-0.247 0.159c-11.212 7.698-25.077 12.294-40.016 12.294-0.332 0-0.663-0.002-0.994-0.007l0.050 0.001c-34.182-0.131-62.713-24.169-69.75-56.255l-0.087-0.474c-138.493-22.099-254.554-103.131-323.456-216.080l-1.152-2.032c-3.778 0.747-8.123 1.175-12.567 1.175-9.827 0-19.165-2.091-27.595-5.852l0.431 0.172c-26.103-11.293-44.035-36.823-44.035-66.543 0-39.927 32.367-72.294 72.294-72.294s72.294 32.367 72.294 72.294c0 20.824-8.804 39.592-22.894 52.784l-0.040 0.037c63.358 102.396 166.932 174.75 288.137 195.238l2.474 0.346s0-1.434 0-2.048c11.157-25.972 36.517-43.831 66.049-43.831 0.252 0 0.503 0.001 0.754 0.004h-0.038c0.281-0.004 0.612-0.006 0.944-0.006 14.939 0 28.804 4.596 40.259 12.452l-0.243-0.157c7.728 5.259 14.197 11.728 19.296 19.207l0.16 0.249c2.682 3.948 5.090 8.467 6.998 13.24l0.17 0.482c101.026-16.869 187.935-67.795 250.419-140.354l0.461-0.548c-9.791-12.103-15.719-27.684-15.719-44.649 0-39.362 31.909-71.27 71.27-71.27 0.198 0 0.396 0.001 0.594 0.002h-0.030c0.015 0 0.033 0 0.051 0 5.056 0 9.99 0.522 14.752 1.515l-0.467-0.081c19.62-46.693 31.019-100.964 31.019-157.897 0-51.76-9.422-101.32-26.644-147.058l0.95 2.875c-3.225 0.59-6.936 0.928-10.725 0.928-9.403 0-18.32-2.078-26.319-5.799l0.385 0.161c-25.94-11.145-43.784-36.456-43.827-65.94v-0.006c0-39.588 32.092-71.68 71.68-71.68s71.68 32.092 71.68 71.68v0 0c0.001 0.129 0.001 0.282 0.001 0.436 0 14.807-4.515 28.559-12.244 39.955l0.16-0.25zM637.338 500.429c-5.189 5.188-12.356 8.398-20.274 8.398-0.072 0-0.145 0-0.217-0.001h0.011c-0.187 0.005-0.408 0.007-0.629 0.007-7.786 0-14.82-3.218-19.845-8.397l-0.007-0.007-36.864-36.659 92.365-92.16 36.864 36.864c5.067 5.137 8.196 12.196 8.196 19.987 0 0.173-0.002 0.347-0.005 0.519v-0.026c0 0.024 0 0.053 0 0.082 0 7.92-3.122 15.11-8.202 20.408l0.010-0.010zM361.677 265.114v-92.365h92.365l184.525 184.73-92.365 92.16-184.525-184.525zM299.008 91.853c-3.082 1.95-6.833 3.107-10.854 3.107-11.33 0-20.515-9.185-20.515-20.515 0-7.309 3.822-13.724 9.576-17.358l0.085-0.050c3.050-1.901 6.752-3.028 10.718-3.028 7.27 0 13.656 3.788 17.29 9.499l0.049 0.083c1.994 3.104 3.178 6.892 3.178 10.958 0 7.247-3.765 13.616-9.445 17.256l-0.082 0.049zM102.4 311.194c8.784 2.437 15.124 10.362 15.124 19.767 0 2.005-0.288 3.942-0.825 5.773l0.036-0.145c-2.519 8.66-10.383 14.881-19.7 14.881-1.954 0-3.845-0.274-5.635-0.785l0.144 0.035c-9.005-2.283-15.56-10.316-15.56-19.88 0-2.038 0.298-4.007 0.852-5.865l-0.037 0.145c2.491-8.702 10.376-14.964 19.723-14.964 0.266 0 0.531 0.005 0.795 0.015l-0.038-0.001c1.888 0.167 3.628 0.529 5.287 1.071l-0.167-0.047zM83.968 391.27c10.421 1.048 18.489 9.773 18.489 20.383 0 0.539-0.021 1.072-0.062 1.601l0.004-0.070c-0.84 10.616-9.661 18.912-20.42 18.912-0.598 0-1.19-0.026-1.775-0.076l0.076 0.005c-10.962-0.452-19.677-9.448-19.677-20.48 0-11.32 9.177-20.496 20.496-20.496 0.288 0 0.575 0.006 0.86 0.018l-0.041-0.001zM81.92 472.986h2.048c10.861 0.561 19.456 9.504 19.456 20.454 0 11.311-9.169 20.48-20.48 20.48s-20.48-9.169-20.48-20.48c0-10.951 8.595-19.894 19.406-20.452l0.050-0.002zM208.077 137.933c-3.68-3.702-5.955-8.804-5.955-14.438s2.275-10.737 5.956-14.439l-0.001 0.001c3.635-3.41 8.534-5.51 13.923-5.53h0.004c6.006 0.015 11.403 2.613 15.14 6.741l0.016 0.018c3.897 3.735 6.32 8.982 6.32 14.796 0 11.311-9.169 20.48-20.48 20.48-6.426 0-12.16-2.959-15.915-7.59l-0.030-0.038zM368.64 56.832c-2.218 0.878-4.787 1.387-7.475 1.387-11.511 0-20.843-9.332-20.843-20.843 0-8.823 5.482-16.365 13.226-19.407l0.142-0.049c2.143-0.907 4.635-1.434 7.25-1.434 0.043 0 0.086 0 0.129 0h-0.007c0.043 0 0.093-0.001 0.144-0.001 8.659 0 16.063 5.374 19.059 12.969l0.048 0.139c1.042 2.374 1.648 5.141 1.648 8.050 0 8.74-5.475 16.201-13.182 19.141l-0.141 0.047z" /> <glyph unicode="" glyph-name="tableau-de-bord" d="M349.594 623.923c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.373c22.622 0 40.96-18.338 40.96-40.96v0zM349.594 407.45c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.51c0 22.622 18.338 40.96 40.96 40.96v0h135.373c22.622 0 40.96-18.338 40.96-40.96v0zM349.594 190.976c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.373c22.622 0 40.96-18.338 40.96-40.96v0zM620.339 623.923c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.168c22.622 0 40.96-18.338 40.96-40.96v0zM620.339 407.45c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.51c0 22.622 18.338 40.96 40.96 40.96v0h135.168c22.622 0 40.96-18.338 40.96-40.96v0zM620.339 190.976c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.168c22.622 0 40.96-18.338 40.96-40.96v0zM890.88 623.923c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.373c22.622 0 40.96-18.338 40.96-40.96v0zM890.88 407.040c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.373c22.622 0 40.96-18.338 40.96-40.96v0zM890.88 190.566c0-22.622-18.338-40.96-40.96-40.96v0h-134.963c-22.622 0-40.96 18.338-40.96 40.96v0 81.92c0 22.622 18.338 40.96 40.96 40.96v0h135.373c22.622 0 40.96-18.338 40.96-40.96v0z" /> -<glyph unicode="" glyph-name="synchro-ilico" d="M581.837 816.64l-139.674 143.36v-122.88c-0.227 0-0.496 0.001-0.765 0.001-214.905 0-389.12-174.215-389.12-389.12 0-134.343 68.080-252.785 171.622-322.707l1.379-0.878 22.733 33.997c-93.932 63.339-154.888 169.345-154.888 289.588 0 192.283 155.877 348.16 348.16 348.16 0.309 0 0.618 0 0.927-0.001h-0.048v-122.88zM277.914 537.907q0 17.818 20.48 17.818t20.48-17.818c0.024-0.344 0.038-0.745 0.038-1.15 0-4.734-1.89-9.027-4.956-12.165l0.003 0.003c-3.533-2.979-8.136-4.789-13.161-4.789-0.63 0-1.253 0.028-1.869 0.084l0.079-0.006q-21.094 0-21.094 18.022zM316.006 369.152h-36.454v133.939h36.659zM390.963 368.947h-36.454v186.778h36.659zM427.827 537.907q0 17.818 20.48 17.818t20.48-17.818c0.024-0.344 0.038-0.745 0.038-1.15 0-4.734-1.89-9.027-4.956-12.165l0.003 0.003c-3.533-2.979-8.136-4.789-13.161-4.789-0.63 0-1.253 0.028-1.869 0.084l0.079-0.006q-21.094 0-21.094 18.022zM465.92 369.152h-35.84v133.939h36.659zM558.899 366.080q-62.669 0-62.669 68.813c-0.112 1.517-0.176 3.287-0.176 5.071 0 18.008 6.492 34.5 17.264 47.262l-0.090-0.109c11.504 11.229 27.251 18.156 44.616 18.156 1.452 0 2.892-0.048 4.32-0.144l-0.194 0.010c0.164 0.001 0.357 0.002 0.551 0.002 14.696 0 28.597-3.402 40.958-9.461l-0.549 0.243-9.011-27.443q-8.602 3.482-16.179 5.734c-4.469 1.413-9.611 2.236-14.942 2.253h-0.009q-28.467 0-28.467-40.96t28.467-39.322c0.394-0.009 0.859-0.013 1.325-0.013 6.836 0 13.426 1.053 19.616 3.006l-0.461-0.126c6.87 2.249 12.839 5.237 18.262 8.962l-0.24-0.156v-31.334c-5.173-3.395-11.184-6.071-17.614-7.695l-0.409-0.087c-6.923-1.697-14.871-2.671-23.047-2.671-0.466 0-0.931 0.003-1.395 0.009l0.070-0.001zM752.64 436.326c0.082-1.289 0.129-2.795 0.129-4.311 0-17.949-6.559-34.365-17.412-46.984l0.080 0.095c-11.359-11.45-27.099-18.537-44.495-18.537-1.278 0-2.547 0.038-3.806 0.114l0.173-0.008c-0.416-0.009-0.906-0.014-1.397-0.014-11.964 0-23.2 3.118-32.939 8.586l0.339-0.175c-9.865 5.717-17.665 14.043-22.586 24.041l-0.147 0.331c-5.049 10.329-8.002 22.476-8.002 35.313 0 0.545 0.005 1.090 0.016 1.632l-0.001-0.081c-0.088 1.331-0.138 2.886-0.138 4.452 0 17.905 6.565 34.277 17.419 46.839l-0.077-0.091c11.402 11.337 27.119 18.345 44.473 18.345 1.358 0 2.706-0.043 4.043-0.127l-0.183 0.009c0.386 0.008 0.84 0.012 1.295 0.012 11.998 0 23.268-3.117 33.044-8.585l-0.343 0.176c9.848-5.656 17.644-13.913 22.586-23.841l0.147-0.326c4.927-10.221 7.806-22.224 7.806-34.9 0-0.691-0.009-1.379-0.026-2.066l0.002 0.102zM659.866 436.326c-0.044-0.833-0.069-1.808-0.069-2.789 0-9.7 2.452-18.827 6.77-26.795l-0.147 0.297c4.256-6.231 11.327-10.268 19.34-10.268 0.401 0 0.8 0.010 1.196 0.030l-0.056-0.002c0.404-0.025 0.876-0.040 1.351-0.040 7.954 0 14.962 4.048 19.077 10.197l0.052 0.083c4.075 7.663 6.469 16.756 6.469 26.408 0 1.301-0.043 2.592-0.129 3.871l0.009-0.173c0.072 1.055 0.112 2.287 0.112 3.529 0 9.662-2.469 18.748-6.811 26.66l0.145-0.288c-5.106 6.068-12.704 9.897-21.197 9.897s-16.091-3.829-21.162-9.855l-0.034-0.042c-3.28-6.858-5.197-14.907-5.197-23.403 0-2.58 0.177-5.119 0.519-7.605l-0.032 0.288zM581.837-64l-139.674 143.36 139.674 143.36v-122.88c0.017 0 0.037 0 0.058 0 192.283 0 348.16 155.877 348.16 348.16 0 120.242-60.955 226.247-153.651 288.801l-1.236 0.786 23.552 33.997c104.921-70.8 173.001-189.242 173.001-323.585 0-214.905-174.215-389.12-389.12-389.12-0.269 0-0.537 0-0.806 0.001h0.041z" /> +<glyph unicode="" glyph-name="synchro" d="M581.837-64l-139.674 143.36 139.674 143.36v-122.88c0.017 0 0.037 0 0.058 0 192.283 0 348.16 155.877 348.16 348.16 0 120.242-60.955 226.247-153.651 288.801l-1.236 0.786 23.552 33.997c104.921-70.8 173.001-189.242 173.001-323.585 0-214.905-174.215-389.12-389.12-389.12-0.269 0-0.537 0-0.806 0.001h0.041zM581.837 816.64l-139.674 143.36v-122.88c-0.227 0-0.496 0.001-0.765 0.001-214.905 0-389.12-174.215-389.12-389.12 0-134.343 68.080-252.785 171.622-322.707l1.379-0.878 22.733 33.997c-93.932 63.339-154.888 169.345-154.888 289.588 0 192.283 155.877 348.16 348.16 348.16 0.309 0 0.618 0 0.927-0.001h-0.048v-122.88z" /> <glyph unicode="" glyph-name="synchro-icar" d="M581.837-64l-139.674 143.36 139.674 143.36v-122.88c0.017 0 0.037 0 0.058 0 192.283 0 348.16 155.877 348.16 348.16 0 120.242-60.955 226.247-153.651 288.801l-1.236 0.786 23.552 33.997c104.921-70.8 173.001-189.242 173.001-323.585 0-214.905-174.215-389.12-389.12-389.12-0.269 0-0.537 0-0.806 0.001h0.041zM581.837 816.64l-139.674 143.36v-122.88c-0.227 0-0.496 0.001-0.765 0.001-214.905 0-389.12-174.215-389.12-389.12 0-134.343 68.080-252.785 171.622-322.707l1.379-0.878 22.733 33.997c-93.932 63.339-154.888 169.345-154.888 289.588 0 192.283 155.877 348.16 348.16 348.16 0.309 0 0.618 0 0.927-0.001h-0.048v-122.88zM299.008 537.907q0 17.818 20.48 17.818t20.48-17.818c0.024-0.344 0.038-0.745 0.038-1.15 0-4.734-1.89-9.027-4.956-12.165l0.003 0.003c-3.533-2.979-8.136-4.789-13.161-4.789-0.63 0-1.253 0.028-1.869 0.084l0.079-0.006q-21.094 0-21.094 18.022zM337.101 369.152h-36.659v133.939h36.659zM430.080 366.080q-62.669 0-62.669 68.813c-0.112 1.517-0.176 3.287-0.176 5.071 0 18.008 6.492 34.5 17.264 47.262l-0.090-0.109c11.504 11.229 27.251 18.156 44.616 18.156 1.452 0 2.892-0.048 4.32-0.144l-0.194 0.010c0.164 0.001 0.357 0.002 0.551 0.002 14.696 0 28.597-3.402 40.958-9.461l-0.549 0.243-10.035-27.443q-8.602 3.482-16.179 5.734c-4.469 1.413-9.611 2.236-14.942 2.253h-0.009q-28.467 0-28.467-40.96t28.467-39.322c0.394-0.009 0.859-0.013 1.325-0.013 6.836 0 13.426 1.053 19.616 3.006l-0.461-0.126c6.704 2.019 12.546 4.737 17.897 8.157l-0.284-0.17v-31.334c-5.172-3.399-11.184-6.076-17.615-7.696l-0.407-0.087c-5.864-1.209-12.603-1.902-19.503-1.902-1.208 0-2.41 0.021-3.608 0.063l0.174-0.005zM586.957 368.947l-6.963 17.613c-5.005-6.763-11.38-12.222-18.745-16.037l-0.301-0.142c-6.763-2.787-14.615-4.405-22.844-4.405-1.258 0-2.506 0.038-3.745 0.112l0.17-0.008c-0.73-0.046-1.584-0.073-2.443-0.073-10.874 0-20.759 4.237-28.092 11.151l0.020-0.019c-6.917 7.519-11.157 17.595-11.157 28.662 0 1.013 0.035 2.017 0.105 3.012l-0.008-0.134c-0.059 0.767-0.093 1.66-0.093 2.562 0 11.93 5.896 22.483 14.934 28.903l0.109 0.074c12.294 7.103 27.043 11.294 42.77 11.294 0.804 0 1.605-0.011 2.404-0.033l-0.118 0.003h23.347v5.939c0.102 0.725 0.16 1.562 0.16 2.413 0 10.067-8.161 18.227-18.227 18.227-0.851 0-1.688-0.058-2.508-0.171l0.095 0.011c-13.895-0.602-26.841-4.090-38.44-9.875l0.552 0.249-12.083 24.781c14.51 7.621 31.702 12.093 49.939 12.093 0.515 0 1.030-0.004 1.544-0.011l-0.078 0.001c1.186 0.079 2.572 0.123 3.968 0.123 13.871 0 26.709-4.42 37.184-11.928l-0.192 0.131c8.901-7.985 14.475-19.523 14.475-32.364 0-1.223-0.051-2.435-0.15-3.633l0.010 0.157v-89.498zM576.102 430.387h-14.131c-0.493 0.019-1.072 0.030-1.653 0.030-8.119 0-15.739-2.138-22.328-5.882l0.224 0.117c-4.755-3.292-7.83-8.719-7.83-14.864 0-0.463 0.017-0.922 0.052-1.376l-0.004 0.060q0-15.565 17.818-15.565c0.442-0.025 0.96-0.039 1.48-0.039 7.339 0 14.019 2.817 19.019 7.429l-0.019-0.018c4.697 4.674 7.604 11.144 7.604 18.292 0 0.409-0.010 0.816-0.028 1.221l0.002-0.057zM725.197 505.754c0.379 0.009 0.825 0.014 1.273 0.014 3.896 0 7.703-0.378 11.388-1.098l-0.373 0.061-2.662-34.406c-3 0.786-6.444 1.238-9.994 1.238-0.303 0-0.605-0.003-0.906-0.010l0.045 0.001c-0.675 0.042-1.464 0.066-2.258 0.066-9.537 0-18.272-3.431-25.039-9.125l0.059 0.048c-6.098-5.925-9.882-14.203-9.882-23.365 0-0.642 0.019-1.28 0.055-1.913l-0.004 0.087v-68.198h-36.659v133.939h27.648l5.325-22.528h1.843c4.093 7.467 9.767 13.6 16.605 18.11l0.189 0.117c6.412 4.363 14.327 6.966 22.85 6.966 0.175 0 0.349-0.001 0.523-0.003h-0.026z" /> <glyph unicode="" glyph-name="reseau" d="M977.51 251.597c26.584 59.488 42.066 128.918 42.066 201.963 0 103.349-30.993 199.459-84.191 279.543l1.165-1.865c8.182 11.447 13.082 25.728 13.082 41.153 0 39.362-31.909 71.27-71.27 71.27-12.124 0-23.54-3.027-33.535-8.367l0.383 0.187c-88.479 77.598-205.176 124.937-332.929 124.937-155.213 0-294.106-69.876-386.936-179.887l-0.621-0.756c-7.012 2.526-15.104 3.987-23.537 3.987-39.475 0-71.475-32.001-71.475-71.475 0-19.503 7.811-37.181 20.475-50.077l-0.010 0.011c-28.679-61.497-45.412-133.512-45.412-209.436 0-95.079 26.242-184.028 71.877-260.001l-1.275 2.288c-6.678-10.339-10.648-22.972-10.648-36.532 0-37.552 30.442-67.994 67.994-67.994 7.54 0 14.794 1.227 21.572 3.493l-0.48-0.139c91.659-91.687 218.299-148.399 358.186-148.399 160.251 0 303.119 74.426 395.924 190.597l0.783 1.014c6.094-2.102 13.12-3.368 20.426-3.481l0.054-0.001c0.141-0.001 0.308-0.002 0.475-0.002 37.891 0 68.608 30.717 68.608 68.608 0 19.37-8.027 36.864-20.936 49.34l-0.020 0.019zM940.851 270.029c-3.742 0.74-8.044 1.163-12.445 1.163-31.627 0-58.149-21.857-65.285-51.289l-0.094-0.459c-152.576 0-287.949 56.73-399.974 133.325 4.965 8.943 7.887 19.611 7.887 30.963 0 7.388-1.238 14.488-3.518 21.102l0.136-0.455 365.158 311.501c11.97-9.766 27.415-15.683 44.243-15.683 9.599 0 18.748 1.925 27.082 5.41l-0.464-0.172c46.912-71.411 74.829-158.943 74.829-252.995 0-65.983-13.74-128.756-38.515-185.615l1.165 3zM512 919.040c0.097 0 0.211 0 0.325 0 115.788 0 221.672-42.404 302.969-112.531l-0.599 0.505c-5.939-9.892-9.452-21.827-9.452-34.584 0-8.16 1.437-15.984 4.073-23.233l-0.15 0.473-364.954-311.501c-11.41 9.098-26.039 14.598-41.953 14.598-17.258 0-33.006-6.469-44.95-17.114l0.067 0.059c-75.968 67.814-143.303 142.429-202.013 223.686l-2.787 4.052c12.214 12.682 19.737 29.955 19.737 48.985 0 16.15-5.418 31.034-14.536 42.935l0.124-0.169c85.806 100.438 212.55 163.733 354.080 163.84h0.019zM86.221 642.355c4.797-1.221 10.304-1.921 15.974-1.921s11.177 0.7 16.439 2.020l-0.465-0.099c64.986-90.797 136.529-170.074 216.023-240.585l1.474-1.284c-1.426-5.076-2.245-10.906-2.245-16.926 0-12.023 3.268-23.282 8.964-32.938l-0.165 0.303-163.84-139.878c-11.916 10.631-27.72 17.127-45.041 17.127-9.626 0-18.783-2.006-27.076-5.623l0.437 0.17c-38.346 66.114-60.976 145.471-60.976 230.113 0 68.715 14.915 133.947 41.684 192.635l-1.188-2.907zM512-13.619c-0.055 0-0.12 0-0.186 0-125.876 0-240.057 50.071-323.713 131.383l0.11-0.106c8.961 11.164 14.382 25.504 14.382 41.109 0 6.14-0.839 12.083-2.409 17.722l0.11-0.464 171.622 146.432c8.912-4.75 19.49-7.539 30.72-7.539s21.808 2.79 31.079 7.713l-0.359-0.174c119.603-81.306 262.963-144.384 426.803-144.384h5.12c2.593-6.654 5.905-12.403 9.943-17.557l-0.112 0.149c-86.016-106.627-216.662-174.268-363.108-174.285h-0.003z" /> <glyph unicode="" glyph-name="rapport-de-controle" d="M536.781 960h-467.763v-1002.496h527.565c-24.786 11.033-46.126 24.762-65.024 41.222l0.308-0.262h-421.888v920.576h397.312v-241.459h241.459v-234.496c15.787-3.204 29.504-7.303 42.682-12.474l-1.722 0.595v275.866zM548.25 890.573l172.032-172.032h-172.032zM210.33 619.008h437.862v-41.984h-437.862v41.984zM563.405 408.883h-353.075v-40.96h301.67c15.016 15.482 31.885 29.029 50.287 40.322l1.118 0.638zM210.33 304.64v-40.96h242.688c4.702 15.893 10.133 29.474 16.639 42.42l-0.665-1.46zM210.33 513.946h437.862v-41.984h-437.862v41.984zM698.982 407.040c118.763 0 215.040-96.277 215.040-215.040s-96.277-215.040-215.040-215.040c-118.763 0-215.040 96.277-215.040 215.040v0c0.117 118.716 96.324 214.923 215.029 215.040h0.011zM698.982 448c-141.385 0-256-114.615-256-256s114.615-256 256-256c141.385 0 256 114.615 256 256v0c0 141.385-114.615 256-256 256v0zM667.853 86.733c-5.799 0.023-11.026 2.452-14.738 6.341l-0.008 0.008-85.606 88.678c-3.557 3.681-5.749 8.701-5.749 14.234 0 11.319 9.176 20.495 20.495 20.495 5.787 0 11.013-2.398 14.74-6.255l0.006-0.006 70.042-72.499 119.603 136.806c3.784 4.313 9.307 7.021 15.462 7.021 11.343 0 20.538-9.195 20.538-20.538 0-5.187-1.923-9.925-5.095-13.54l0.020 0.023-134.144-153.6c-3.678-4.174-8.991-6.831-14.928-6.963h-0.023z" /> diff --git a/app/assets/fonts/sBoiv/sboiv.ttf b/app/assets/fonts/sBoiv/sboiv.ttf Binary files differindex d6b4d8441..a04a1e09a 100644..100755 --- a/app/assets/fonts/sBoiv/sboiv.ttf +++ b/app/assets/fonts/sBoiv/sboiv.ttf diff --git a/app/assets/fonts/sBoiv/sboiv.woff b/app/assets/fonts/sBoiv/sboiv.woff Binary files differindex 8e10ad072..5ceec6b78 100644..100755 --- a/app/assets/fonts/sBoiv/sboiv.woff +++ b/app/assets/fonts/sBoiv/sboiv.woff diff --git a/app/assets/javascripts/forms.coffee b/app/assets/javascripts/forms.coffee index b7ae3c6ca..9543220d0 100644 --- a/app/assets/javascripts/forms.coffee +++ b/app/assets/javascripts/forms.coffee @@ -25,7 +25,7 @@ isEdge = !isIE && !!window.StyleMedia if $('.page-action').children('.formSubmitr').length > 0 $('.page-action').children('.formSubmitr').remove() - $('.formSubmitr').appendTo('.page-action') + $('.formSubmitr').appendTo('.page-action').addClass('sticky-action') if isIE || isEdge $('.formSubmitr').off() diff --git a/app/assets/stylesheets/OpenLayers/custom.sass b/app/assets/stylesheets/OpenLayers/custom.sass index 013c056d6..0675b0ba6 100644 --- a/app/assets/stylesheets/OpenLayers/custom.sass +++ b/app/assets/stylesheets/OpenLayers/custom.sass @@ -28,6 +28,7 @@ font-size: 0.6em &:hover text-decoration: none + font-weight: bold &.active background: $blue diff --git a/app/assets/stylesheets/components/_tables.sass b/app/assets/stylesheets/components/_tables.sass index 5360f0625..66ffe9cb1 100644 --- a/app/assets/stylesheets/components/_tables.sass +++ b/app/assets/stylesheets/components/_tables.sass @@ -377,6 +377,9 @@ border-top-color: transparent > div:not(.btn-group) min-height: 20px + white-space: nowrap + overflow: hidden + text-overflow: ellipsis .td > .headlined &:before diff --git a/app/assets/stylesheets/modules/_vj_collection.sass b/app/assets/stylesheets/modules/_vj_collection.sass index d9079daa2..3ff0828ea 100644 --- a/app/assets/stylesheets/modules/_vj_collection.sass +++ b/app/assets/stylesheets/modules/_vj_collection.sass @@ -88,10 +88,12 @@ .table-2entries .t2e-head - .detailed-timetables + .detailed-timetables, .detailed-purchase-windows + &:after + left: 0 .fa margin-right: 5px - .detailed-timetables-bt + .detailed-timetables-bt, .detailed-purchase-windows-bt text-decoration: none .fa margin-right: 5px @@ -117,7 +119,7 @@ top: 50% margin-top: -8px - .detailed-timetables + .detailed-timetables, .detailed-purchase-windows padding-top: 10px text-align: left margin-bottom: -5px @@ -129,6 +131,13 @@ a text-decoration: none border: none + white-space: nowrap + overflow: hidden + text-overflow: ellipsis + display: inline-block + margin: 0 + line-height: 1em + max-width: 100% &:before position: absolute left: 0px @@ -147,7 +156,19 @@ padding-top: 8px font-weight: bold - .t2e-item-list .detailed-timetables > div + .detailed-purchase-windows + margin-bottom: 12px + position: relative + &:after + position: absolute + left: -8px + bottom: 0 + right: -8px + content: "" + border-top: 1px solid $lightgrey + + .t2e-item-list .detailed-timetables > div, + .t2e-item-list .detailed-purchase-windows > div, border-left: none &:after top: 50% diff --git a/app/assets/stylesheets/typography/_sboiv.sass b/app/assets/stylesheets/typography/_sboiv.sass index f0943f843..5419708ac 100644 --- a/app/assets/stylesheets/typography/_sboiv.sass +++ b/app/assets/stylesheets/typography/_sboiv.sass @@ -73,12 +73,9 @@ .sb-dashboard:before content: '\e909' -.sb-line_referential:before +.sb-line_referential:before, .sb-stop_area_referential:before content: '\e90a' -.sb-stop_area_referential:before - content: '\e90b' - .sb-network:before content: '\e90c' diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb index 3d7f4ca79..dc2df0697 100644 --- a/app/controllers/api/v1/imports_controller.rb +++ b/app/controllers/api/v1/imports_controller.rb @@ -1,11 +1,11 @@ class Api::V1::ImportsController < Api::V1::IbooController - defaults :resource_class => WorkbenchImport + defaults :resource_class => Import::Workbench belongs_to :workbench def create args = workbench_import_params.merge(creator: 'Webservice') @import = parent.workbench_imports.create(args) - if @import.valid? + if @import.valid? create! else render json: { status: "error", messages: @import.errors.full_messages } diff --git a/app/controllers/api/v1/internals/netex_imports_controller.rb b/app/controllers/api/v1/internals/netex_imports_controller.rb index c8e33f7b8..c2b7b20cc 100644 --- a/app/controllers/api/v1/internals/netex_imports_controller.rb +++ b/app/controllers/api/v1/internals/netex_imports_controller.rb @@ -25,13 +25,13 @@ module Api private def find_netex_import - @netex_import = NetexImport.find(params[:id]) + @netex_import = Import::Netex.find(params[:id]) rescue ActiveRecord::RecordNotFound render json: { - status: "error", + status: "error", message: "Record not found" } - finish_action! + finish_action! end def find_workbench @@ -52,7 +52,7 @@ module Api attributes = attributes.merge referential_id: @new_referential.id - @netex_import = NetexImport.new attributes + @netex_import = Import::Netex.new attributes @netex_import.save! unless @netex_import.referential diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb index d86c1fcd8..2654fa088 100644 --- a/app/controllers/api/v1/netex_imports_controller.rb +++ b/app/controllers/api/v1/netex_imports_controller.rb @@ -34,7 +34,7 @@ module Api attributes = attributes.merge referential_id: @new_referential.id - @netex_import = NetexImport.new attributes + @netex_import = Import::Netex.new attributes @netex_import.save! unless @netex_import.referential diff --git a/app/controllers/concerns/iev_interfaces.rb b/app/controllers/concerns/iev_interfaces.rb new file mode 100644 index 000000000..aa4d3fe6a --- /dev/null +++ b/app/controllers/concerns/iev_interfaces.rb @@ -0,0 +1,69 @@ +module IevInterfaces + extend ActiveSupport::Concern + + included do + before_action only: [:index] { set_date_time_params("started_at", DateTime) } + before_action :ransack_status_params, only: [:index] + respond_to :html + belongs_to :workbench + end + + def show + show! do + instance_variable_set "@#{collection_name.singularize}", resource.decorate(context: { + workbench: @workbench + }) + end + end + + def index + index! do |format| + format.html { + if collection.out_of_bounds? + redirect_to params.merge(:page => 1) + end + collection = decorate_collection(collection) + } + end + end + + protected + + def collection + scope = parent.send(collection_name).where(parent_id: nil) + if index_model.name.demodulize != "Base" + scope = scope.where(type: index_model.name) + end + + scope = self.ransack_period_range(scope: scope, error_message: t("#{collection_name}.filters.error_period_filter"), query: :where_started_at_in) + + @q = scope.search(params[:q]) + + unless instance_variable_get "@#{collection_name}" + coll = if sort_column && sort_direction + @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: 10) + else + @q.result(distinct: true).order(:name).paginate(page: params[:page], per_page: 10) + end + instance_variable_set "@#{collection_name}", decorate_collection(coll) + end + instance_variable_get "@#{collection_name}" + end + + private + def ransack_status_params + if params[:q] + return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (resource_class.status.values & params[:q][:status_eq_any]).length >= 4 ) + params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") + params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") + end + end + + def sort_column + parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'created_at' + end + + def sort_direction + %w[asc desc].include?(params[:direction]) ? params[:direction] : 'desc' + end +end diff --git a/app/controllers/export_tasks_controller.rb b/app/controllers/export_tasks_controller.rb deleted file mode 100644 index b889c1882..000000000 --- a/app/controllers/export_tasks_controller.rb +++ /dev/null @@ -1,87 +0,0 @@ -class ExportTasksController < ChouetteController - include ReferentialSupport - defaults :resource_class => ExportTask - - respond_to :html, :only => [:new, :create] - respond_to :js, :only => [:new, :create] - belongs_to :referential - - def new - @available_exports = available_exports - begin - new! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end - - def create - @available_exports = available_exports - begin - create! do |success, failure| - success.html { redirect_to referential_exports_path(@referential) } - end - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end - - def references - references_type = params[:filter].pluralize - references = @referential.send(references_type).where("name ilike ?", "%#{params[:q]}%").select("id, name") - respond_to do |format| - format.json do - render :json => references.collect { |child| { :id => child.id, :name => child.name } } - end - end - end - - protected - - def available_exports - export_task_parameters = params[:export_task] - if export_task_parameters.present? - @available_exports = [ - export_task_parameters[:data_format] == "neptune" ? build_resource : NeptuneExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "netex" ? build_resource : NetexExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "gtfs" ? build_resource : GtfsExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "hub" ? build_resource : HubExport.new(:referential_id => @referential.id ), - export_task_parameters[:data_format] == "kml" ? build_resource : KmlExport.new(:referential_id => @referential.id ) - ] - else - @available_exports = [ - NeptuneExport.new(:referential_id => @referential.id ), - NetexExport.new(:referential_id => @referential.id ), - GtfsExport.new(:referential_id => @referential.id ), - HubExport.new(:referential_id => @referential.id ), - KmlExport.new(:referential_id => @referential.id ) - ] - end - end - - def build_resource - @export_task ||= if params[:export_task].present? - export_task_parameters = params[:export_task] - case export_task_parameters[:data_format] - when "neptune" - NeptuneExport.new(export_task_parameters) - when "netex" - NetexExport.new(export_task_parameters) - when "gtfs" - GtfsExport.new(export_task_parameters) - when "hub" - HubExport.new(export_task_parameters) - when "kml" - KmlExport.new(export_task_parameters) - end - else - NeptuneExport.new - end - - end - -end diff --git a/app/controllers/exports_controller.rb b/app/controllers/exports_controller.rb index ccc163e34..a5282a514 100644 --- a/app/controllers/exports_controller.rb +++ b/app/controllers/exports_controller.rb @@ -1,74 +1,50 @@ -require 'will_paginate/array' -require 'open-uri' - class ExportsController < ChouetteController - include ReferentialSupport - defaults :resource_class => Export - - respond_to :html, :only => [:show, :index, :destroy, :exported_file] - respond_to :js, :only => [:index] - belongs_to :referential - - def index - begin - index! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) + include PolicyChecker + include RansackDateFilter + include IevInterfaces + skip_before_action :authenticate_user!, only: [:upload] + defaults resource_class: Export::Base, collection_name: 'exports', instance_name: 'export' + + def upload + if params[:token] == resource.token_upload + resource.file = params[:file] + resource.save! + redirect_to [resource.workbench, resource] + else + user_not_authorized end end - def show - begin - show! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end - end + private - def destroy - begin - destroy! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) - end + def index_model + Export::Base end - def exported_file - # WARNING : files under 10kb in size get treated as StringIO by OpenUri - # http://stackoverflow.com/questions/10496874/why-does-openuri-treat-files-under-10kb-in-size-as-stringio - OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax') - OpenURI::Buffer.const_set 'StringMax', 0 - begin - send_file open(resource.file_path), { :type => "application/#{resource.filename_extension}", :disposition => "attachment", :filename => resource.filename } - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t(error.locale_for_error) - redirect_to referential_path(@referential) + def build_resource + Export::Base.force_load_descendants if Rails.env.development? + @export ||= Export::Base.new(*resource_params) do |export| + export.workbench = parent + export.creator = current_user.name end + @export end - protected - - def export_service - ExportService.new(@referential) - end - - def resource - @export ||= export_service.find( params[:id] ) - @line_items = @export.report.line_items - if @line_items.size > 500 - @line_items = @line_items.paginate(page: params[:page], per_page: 20) + def export_params + permitted_keys = %i(name type referential_id) + export_class = params[:export] && params[:export][:type] && params[:export][:type].safe_constantize + if export_class + permitted_keys += export_class.options.keys end - @export + params.require(:export).permit(permitted_keys) end - def collection - @exports ||= export_service.all.sort_by{ |export| export.created_at }.reverse.paginate(:page => params[:page]) + def decorate_collection(exports) + ExportDecorator.decorate( + exports, + context: { + workbench: @workbench + } + ) end end diff --git a/app/controllers/import_messages_controller.rb b/app/controllers/import_messages_controller.rb index 4f8fe7a25..e9a071177 100644 --- a/app/controllers/import_messages_controller.rb +++ b/app/controllers/import_messages_controller.rb @@ -1,15 +1,15 @@ class ImportMessagesController < ChouetteController - defaults resource_class: ImportMessage, collection_name: 'import_messages', instance_name: 'import_message' + defaults resource_class: Import::Message, collection_name: 'import_messages', instance_name: 'import_message' respond_to :csv - belongs_to :import, :parent_class => Import do - belongs_to :import_resource, :parent_class => ImportResource + belongs_to :import, :parent_class => Import::Base do + belongs_to :import_resource, :parent_class => Import::Resource end def index index! do |format| format.csv { - send_data ImportMessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv" + send_data Import::MessageExport.new(:import_messages => @import_messages).to_csv(:col_sep => "\;", :quote_char=>'"', force_quotes: true) , :filename => "import_errors_#{@import_resource.name.gsub('.xml', '')}_#{Date.today.to_s}.csv" } end end @@ -20,7 +20,7 @@ class ImportMessagesController < ChouetteController end def parent - @import_resource ||= ImportResource.find(params[:import_resource_id]) + @import_resource ||= Import::Resource.find(params[:import_resource_id]) end end diff --git a/app/controllers/import_resources_controller.rb b/app/controllers/import_resources_controller.rb index ea78394a1..1535fd171 100644 --- a/app/controllers/import_resources_controller.rb +++ b/app/controllers/import_resources_controller.rb @@ -1,7 +1,7 @@ class ImportResourcesController < ChouetteController - defaults resource_class: ImportResource, collection_name: 'import_resources', instance_name: 'import_resource' + defaults resource_class: Import::Resource, collection_name: 'import_resources', instance_name: 'import_resource' respond_to :html - belongs_to :import + belongs_to :import, :parent_class => Import::Base def index index! do |format| diff --git a/app/controllers/import_tasks_controller.rb b/app/controllers/import_tasks_controller.rb deleted file mode 100644 index 1a349087d..000000000 --- a/app/controllers/import_tasks_controller.rb +++ /dev/null @@ -1,69 +0,0 @@ -class ImportTasksController < ChouetteController - include ReferentialSupport - defaults :resource_class => ImportTask - - respond_to :html, :only => [:new, :create] - respond_to :js, :only => [:new, :create] - belongs_to :referential - - def new - @available_imports = available_imports - begin - new! - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t('iev.exception.default') - redirect_to referential_path(@referential) - end - end - - def create - @available_imports = available_imports - begin - create! do |success, failure| - success.html { redirect_to referential_imports_path(@referential) } - end - rescue Ievkit::Error, Faraday::Error => error - logger.error("Iev failure : #{error.message}") - flash[:error] = t('iev.exception.default') - redirect_to referential_path(@referential) - end - end - - protected - - def available_imports - import_task_parameters = params[:import_task] - - if import_task_parameters.present? - @available_imports = [ - import_task_parameters[:data_format] == "neptune" ? build_resource : NeptuneImport.new(:referential_id => @referential.id ), - import_task_parameters[:data_format] == "netex" ? build_resource : NetexImport.new(:referential_id => @referential.id ), - import_task_parameters[:data_format] == "gtfs" ? build_resource : GtfsImport.new(:referential_id => @referential.id ) - ] - else - @available_imports = [ - NeptuneImport.new(:referential_id => @referential.id ), - NetexImport.new(:referential_id => @referential.id ), - GtfsImport.new(:referential_id => @referential.id ) - ] - end - end - - def build_resource - @import_task ||= if params[:import_task].present? - import_task_parameters = params[:import_task] - case import_task_parameters[:data_format] - when "neptune" - NeptuneImport.new(import_task_parameters) - when "netex" - @import_task = NetexImport.new(import_task_parameters) - when "gtfs" - @import_task = GtfsImport.new(import_task_parameters) - end - else - @import_task = NeptuneImport.new - end - end - -end diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index 7a999d657..8d7a723a0 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -1,31 +1,9 @@ class ImportsController < ChouetteController include PolicyChecker include RansackDateFilter - before_action only: [:index] { set_date_time_params("started_at", DateTime) } + include IevInterfaces skip_before_action :authenticate_user!, only: [:download] - defaults resource_class: Import, collection_name: 'imports', instance_name: 'import' - before_action :ransack_status_params, only: [:index] - respond_to :html - belongs_to :workbench - - def show - show! do - @import = @import.decorate(context: { - workbench: @workbench - }) - end - end - - def index - index! do |format| - format.html { - if collection.out_of_bounds? - redirect_to params.merge(:page => 1) - end - @imports = decorate_imports(@imports) - } - end - end + defaults resource_class: Import::Base, collection_name: 'imports', instance_name: 'import' def download if params[:token] == resource.token_download @@ -35,33 +13,14 @@ class ImportsController < ChouetteController end end - protected - def collection - scope = parent.imports.where(type: "WorkbenchImport") - - scope = self.ransack_period_range(scope: scope, error_message: t('imports.filters.error_period_filter'), query: :where_started_at_in) - - @q = scope.search(params[:q]) - - if sort_column && sort_direction - @imports ||= @q.result(distinct: true).order(sort_column + ' ' + sort_direction).paginate(page: params[:page], per_page: 10) - else - @imports ||= @q.result(distinct: true).order(:name).paginate(page: params[:page], per_page: 10) - end - end - private - def ransack_status_params - if params[:q] - return params[:q].delete(:status_eq_any) if params[:q][:status_eq_any].empty? || ( (Import.status.values & params[:q][:status_eq_any]).length >= 4 ) - params[:q][:status_eq_any].push("new", "running") if params[:q][:status_eq_any].include?("pending") - params[:q][:status_eq_any].push("aborted", "canceled") if params[:q][:status_eq_any].include?("failed") - end + def index_model + Import::Workbench end - + def build_resource - @import ||= WorkbenchImport.new(*resource_params) do |import| + @import ||= Import::Workbench.new(*resource_params) do |import| import.workbench = parent import.creator = current_user.name end @@ -76,14 +35,7 @@ class ImportsController < ChouetteController ) end - def sort_column - parent.imports.column_names.include?(params[:sort]) ? params[:sort] : 'created_at' - end - def sort_direction - %w[asc desc].include?(params[:direction]) ? params[:direction] : 'desc' - end - - def decorate_imports(imports) + def decorate_collection(imports) ImportDecorator.decorate( imports, context: { diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index e38a92982..152da4fa2 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -5,7 +5,7 @@ class StatusesController < ChouetteController status = { referentials_blocked: Referential.blocked.count, - imports_blocked: Import.blocked.count, + imports_blocked: Import::Base.blocked.count, compliance_check_sets_blocked: ComplianceCheckSet.blocked.count } status[:status] = global_status status diff --git a/app/controllers/stop_areas_controller.rb b/app/controllers/stop_areas_controller.rb index 41a1a8c6d..c77500132 100644 --- a/app/controllers/stop_areas_controller.rb +++ b/app/controllers/stop_areas_controller.rb @@ -53,6 +53,7 @@ class StopAreasController < ChouetteController index! do |format| format.html { + # binding.pry if collection.out_of_bounds? redirect_to params.merge(:page => 1) end @@ -133,7 +134,9 @@ class StopAreasController < ChouetteController end def collection - @q = parent.present? ? parent.stop_areas.search(params[:q]) : referential.stop_areas.search(params[:q]) + scope = parent.present? ? parent.stop_areas : referential.stop_areas + scope = ransack_status(scope) + @q = scope.search(params[:q]) if sort_column && sort_direction @stop_areas ||= @@ -202,8 +205,28 @@ class StopAreasController < ChouetteController :waiting_time, :zip_code, :kind, + :status, localized_names: Chouette::StopArea::AVAILABLE_LOCALIZATIONS ) end + # Fake ransack filter + def ransack_status scope + return scope unless params[:q].try(:[], :status) + return scope if params[:q][:status].values.uniq.length == 1 + + @status = { + in_creation: params[:q][:status]['in_creation'] == 'true', + confirmed: params[:q][:status]['confirmed'] == 'true', + deactivated: params[:q][:status]['deactivated'] == 'true', + } + + scope = Chouette::StopArea.where( + "confirmed_at #{@status[:confirmed] ? "IS NOT NULL" : "IS NULL"} + AND deleted_at #{@status[:deactivated] ? "IS NOT NULL" : "IS NULL"}" + ) + + params[:q].delete :status + scope + end end diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index 14795227c..e532712d2 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -155,6 +155,10 @@ class VehicleJourneysController < ChouetteController private def load_custom_fields @custom_fields = referential.workgroup&.custom_fields_definitions || {} + + @extra_headers = Rails.application.config.vehicle_journeys_extra_headers.dup.delete_if do |header| + header[:type] == :custom_field and not @custom_fields.has_key?(header[:name].to_s) + end end def map_stop_points points diff --git a/app/decorators/export_decorator.rb b/app/decorators/export_decorator.rb new file mode 100644 index 000000000..26682f383 --- /dev/null +++ b/app/decorators/export_decorator.rb @@ -0,0 +1,21 @@ +class ExportDecorator < AF83::Decorator + decorates Export::Base + + set_scope { context[:workbench] } + + define_instance_method :export_status_css_class do + cls ='' + cls = 'overheaded-success' if object.status == 'successful' + cls = 'overheaded-warning' if object.status == 'warning' + cls = 'overheaded-danger' if %w[failed aborted canceled].include? object.status + cls + end + + create_action_link do |l| + l.content t('exports.actions.new') + end + + with_instance_decorator do |instance_decorator| + instance_decorator.show_action_link + end +end diff --git a/app/decorators/import_decorator.rb b/app/decorators/import_decorator.rb index 1964365ae..a20dfbc42 100644 --- a/app/decorators/import_decorator.rb +++ b/app/decorators/import_decorator.rb @@ -1,5 +1,5 @@ class ImportDecorator < AF83::Decorator - decorates Import + decorates Import::Base set_scope { context[:workbench] } diff --git a/app/helpers/exports_helper.rb b/app/helpers/exports_helper.rb index 8ac494cfc..4e92c7e38 100644 --- a/app/helpers/exports_helper.rb +++ b/app/helpers/exports_helper.rb @@ -1,6 +1,25 @@ # -*- coding: utf-8 -*- module ExportsHelper - + def export_status status + import_status status + end + + def export_option_input form, export, attr, option_def, type + opts = { required: option_def[:required], input_html: {value: export.try(attr) || option_def[:default_value]}, as: option_def[:type], selected: export.try(attr) || option_def[:default_value]} + opts[:collection] = option_def[:collection] if option_def.has_key?(:collection) + opts[:collection] = export.instance_exec(&option_def[:collection]) if option_def[:collection].is_a?(Proc) + opts[:label] = t "activerecord.attributes.export.#{type.name.demodulize.underscore}.#{attr}" + form.input attr, opts + end + + def export_message_content message + if message.message_key == "full_text" + message.message_attributes["text"] + else + t([message.class.name.underscore.gsub('/', '_').pluralize, message.message_key].join('.'), message.message_attributes.symbolize_keys) + end + end + def fields_for_export_task_format(form) begin render :partial => export_partial_name(form), :locals => { :form => form } @@ -8,7 +27,7 @@ module ExportsHelper "" end end - + def export_partial_name(form) "fields_#{form.object.format.underscore}_export" end @@ -22,7 +41,7 @@ module ExportsHelper end.join.html_safe end end - + def compliance_icon( export_task) return nil unless export_task.compliance_check_task export_task.compliance_check_task.tap do |cct| @@ -33,5 +52,5 @@ module ExportsHelper end end end - + end diff --git a/app/helpers/stop_areas_helper.rb b/app/helpers/stop_areas_helper.rb index 05ae042f5..1c9d974a1 100644 --- a/app/helpers/stop_areas_helper.rb +++ b/app/helpers/stop_areas_helper.rb @@ -54,4 +54,40 @@ module StopAreasHelper end end + def stop_area_registration_number_title stop_area + if stop_area&.stop_area_referential&.registration_number_format.present? + return t("formtastic.titles.stop_area.registration_number_format", registration_number_format: stop_area.stop_area_referential.registration_number_format) + end + t "formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number" + end + + def stop_area_registration_number_is_required stop_area + val = format_restriction_for_locales(@referential) == '.hub' + val ||= stop_area&.stop_area_referential&.registration_number_format.present? + val + end + + def stop_area_registration_number_value stop_area + stop_area&.registration_number || stop_area&.stop_area_referential&.generate_registration_number + end + + def stop_area_status(stop_area) + if stop_area.activated? + content_tag(:span, nil, class: 'fa fa-check-circle fa-lg text-success') + + t('activerecord.attributes.stop_area.confirmed') + elsif stop_area.deactivated? + content_tag(:span, nil, class: 'fa fa-exclamation-circle fa-lg text-danger') + + t('activerecord.attributes.stop_area.deactivated') + else + content_tag(:span, nil, class: 'fa fa-pencil fa-lg text-info') + + t('activerecord.attributes.stop_area.in_creation') + end + end + + def stop_area_status_options + Chouette::StopArea.statuses.map do |status| + [ t(status, scope: 'activerecord.attributes.stop_area'), status ] + end + end + end diff --git a/app/helpers/table_builder_helper/column.rb b/app/helpers/table_builder_helper/column.rb index f930eb9fd..f16fc66be 100644 --- a/app/helpers/table_builder_helper/column.rb +++ b/app/helpers/table_builder_helper/column.rb @@ -29,7 +29,9 @@ module TableBuilderHelper return @name if @name.present? # Transform `Chouette::Line` into "line" - model_key = model.to_s.demodulize.underscore + model_key = model.to_s.underscore + model_key.gsub! 'chouette/', '' + model_key.gsub! '/', '.' I18n.t("activerecord.attributes.#{model_key}.#{@key}") end diff --git a/app/javascript/helpers/routes_map.coffee b/app/javascript/helpers/routes_map.coffee index 42377cd6e..de6196372 100644 --- a/app/javascript/helpers/routes_map.coffee +++ b/app/javascript/helpers/routes_map.coffee @@ -29,10 +29,10 @@ RoutesLayersControl = (routes, routes_map) -> element.className = 'ol-unselectable ol-routes-layers hidden' Object.keys(routes).forEach (id)=> route = routes[id] - route.active = true + route.active = false label = document.createElement('a') label.title = route.name - label.className = 'active' + label.className = '' label.innerHTML = route.name element.appendChild label label.addEventListener "click", => @@ -140,13 +140,13 @@ class RoutesMap @map.addLayer vectorEdgesLayer @map.addLayer vectorLnsLayer - lineStyle: (active=true)-> + lineStyle: (active=false)-> new ol.style.Style stroke: new ol.style.Stroke color: '#007fbb' width: if active then 3 else 0 - edgeStyles: (active=true)-> + edgeStyles: (active=false)-> new ol.style.Style image: new ol.style.Circle radius: 5 @@ -157,7 +157,7 @@ class RoutesMap color: '#007fbb' width: if active then 3 else 0 - defaultStyles: (active=true)-> + defaultStyles: (active=false)-> new ol.style.Style image: new ol.style.Circle radius: 4 diff --git a/app/javascript/journey_patterns/components/JourneyPattern.js b/app/javascript/journey_patterns/components/JourneyPattern.js index 35765b99a..d20294594 100644 --- a/app/javascript/journey_patterns/components/JourneyPattern.js +++ b/app/javascript/journey_patterns/components/JourneyPattern.js @@ -74,17 +74,22 @@ export default class JourneyPattern extends Component{ return !this.props.status.policy[`journey_patterns.${action}`] } - totals(){ + totals(onlyCommercial=false){ let totalTime = 0 let totalDistance = 0 let from = null this.props.value.stop_points.map((stopPoint, i) =>{ - if(from && stopPoint.checked){ + let usePoint = stopPoint.checked + console.log(stopPoint) + if(onlyCommercial && (i == 0 || i == this.props.value.stop_points.length - 1) && stopPoint.kind == "non_commercial"){ + usePoint = false + } + if(from && usePoint){ let [costsKey, costs, time, distance] = this.getTimeAndDistanceBetweenStops(from, stopPoint.id) totalTime += time totalDistance += distance } - if(stopPoint.checked){ + if(usePoint){ from = stopPoint.id } }) @@ -109,13 +114,15 @@ export default class JourneyPattern extends Component{ } else{ let hours = parseInt(time/60) - return hours + " h " + (time - 60*hours) + let minutes = (time - 60*hours) + return hours + " h " + (minutes > 0 ? minutes : '') } } render() { this.previousSpId = undefined - let [totalTime, totalDistance] = this.totals() + let [totalTime, totalDistance] = this.totals(false) + let [commercialTotalTime, commercialTotalDistance] = this.totals(true) return ( <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.object_id ? '' : ' to_record') + (this.props.value.errors ? ' has-error': '') + (this.hasFeature('costs_in_journey_patterns') ? ' with-costs' : '')}> <div className='th'> @@ -128,6 +135,12 @@ export default class JourneyPattern extends Component{ <span className="col-md-6"><i className="fa fa-clock-o"></i>{totalTime}</span> </div> } + {this.hasFeature('costs_in_journey_patterns') && + <div className="small row totals commercial"> + <span className="col-md-6"><i className="fa fa-arrows-h"></i>{commercialTotalDistance}</span> + <span className="col-md-6"><i className="fa fa-clock-o"></i>{commercialTotalTime}</span> + </div> + } <div className={this.props.value.deletable ? 'btn-group disabled' : 'btn-group'}> <div className={this.props.value.deletable ? 'btn dropdown-toggle disabled' : 'btn dropdown-toggle'} diff --git a/app/javascript/journey_patterns/components/JourneyPatterns.js b/app/javascript/journey_patterns/components/JourneyPatterns.js index 31727fefc..e8b6bf143 100644 --- a/app/javascript/journey_patterns/components/JourneyPatterns.js +++ b/app/javascript/journey_patterns/components/JourneyPatterns.js @@ -110,6 +110,11 @@ export default class JourneyPatterns extends Component { <div className='strong mb-xs'>ID Mission</div> <div>Code mission</div> <div>Nb arrêts</div> + { this.hasFeature('costs_in_journey_patterns') && + <div> + <div>{I18n.attribute_name('journey_pattern', 'full_journey_time')}</div> + <div>{I18n.attribute_name('journey_pattern', 'commercial_journey_time')}</div> + </div> } </div> {this.props.stopPointsList.map((sp, i) =>{ return ( diff --git a/app/javascript/packs/exports/new.js b/app/javascript/packs/exports/new.js new file mode 100644 index 000000000..ffe702cdb --- /dev/null +++ b/app/javascript/packs/exports/new.js @@ -0,0 +1,3 @@ +import MasterSlave from "../../helpers/master_slave" + +new MasterSlave("form") diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index 5fb88f024..e00e9b1b0 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -212,14 +212,15 @@ const actions = { toggleArrivals : () => ({ type: 'TOGGLE_ARRIVALS', }), - updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => ({ + updateTime : (val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency=false) => ({ type: 'UPDATE_TIME', val, subIndex, index, timeUnit, isDeparture, - isArrivalsToggled + isArrivalsToggled, + enforceConsistency }), resetStateFilters: () => ({ type: 'RESET_FILTERS' @@ -486,10 +487,10 @@ const actions = { vjas.delta = delta return vjas }, - adjustSchedule: (action, schedule) => { + adjustSchedule: (action, schedule, enforceConsistency=false) => { // we enforce that the departure time remains after the arrival time actions.getDelta(schedule) - if(schedule.delta < 0){ + if(enforceConsistency && schedule.delta < 0){ if(action.isDeparture){ schedule.arrival_time = schedule.departure_time } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index d605614c7..73d99d120 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -67,14 +67,35 @@ export default class VehicleJourney extends Component { return found } + hasPurchaseWindow(purchase_windows, window) { + return this.hasTimeTable(purchase_windows, window) + } + isDisabled(bool1, bool2) { return (bool1 || bool2) } + extraHeaderValue(header) { + if(header.type == "custom_field"){ + let field = this.props.value.custom_fields[header["name"]] + if(field.field_type == "list"){ + return field.options.list_values[field.value] + } + else{ + return field.value + } + } + else{ + return this.props.value[header["name"]] + } + } + render() { this.previousCity = undefined let detailed_calendars = this.hasFeature('detailed_calendars') && !this.disabled let detailed_calendars_shown = $('.detailed-timetables-bt').hasClass('active') + let detailed_purchase_windows = this.hasFeature('detailed_purchase_windows') && !this.disabled + let detailed_purchase_windows_shown = $('.detailed-purchase-windows-bt').hasClass('active') let {time_tables, purchase_windows} = this.props.value return ( @@ -89,6 +110,11 @@ export default class VehicleJourney extends Component { <div>{this.props.value.published_journey_name && this.props.value.published_journey_name != I18n.t('undefined') ? this.props.value.published_journey_name : '-'}</div> <div>{this.props.value.journey_pattern.short_id || '-'}</div> <div>{this.props.value.company ? this.props.value.company.name : '-'}</div> + { + this.props.extraHeaders.map((header, i) => + <div key={i}>{this.extraHeaderValue(header)}</div> + ) + } { this.hasFeature('purchase_windows') && <div> {purchase_windows.slice(0,3).map((tt, i)=> @@ -97,6 +123,13 @@ export default class VehicleJourney extends Component { {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} </div> } + { detailed_purchase_windows && + <div className={"detailed-purchase-windows" + (detailed_purchase_windows_shown ? "" : " hidden")}> + {this.props.allPurchaseWindows.map((w, i) => + <div key={i} className={(this.hasPurchaseWindow(purchase_windows, w) ? "active" : "inactive")}></div> + )} + </div> + } <div> {time_tables.slice(0,3).map((tt, i)=> <span key={i} className='vj_tt'>{this.timeTableURL(tt)}</span> @@ -125,6 +158,8 @@ export default class VehicleJourney extends Component { )} </div> } + + </div> {this.props.value.vehicle_journey_at_stops.map((vj, i) => <div key={i} className='td text-center'> @@ -140,6 +175,7 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false)}} + onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', false, false, true)}} value={vj.arrival_time['hour']} /> <span>:</span> @@ -151,6 +187,7 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'minute', false, false, true)}} value={vj.arrival_time['minute']} /> </span> @@ -171,6 +208,7 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, 'hour', true, this.props.filters.toggleArrivals, true)}} value={vj.departure_time['hour']} /> <span>:</span> @@ -182,13 +220,11 @@ export default class VehicleJourney extends Component { disabled={!this.props.editMode || this.isDisabled(this.props.value.deletable, vj.dummy) || this.props.filters.policy['vehicle_journeys.update'] == false} readOnly={!this.props.editMode && !vj.dummy} onChange={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals)}} + onBlur={(e) => {this.props.onUpdateTime(e, i, this.props.index, "minute", true, this.props.filters.toggleArrivals, true)}} value={vj.departure_time['minute']} /> - </span> - </div> - {vj.errors && <div className="errors"> - {vj.errors} - </div>} + </span> + </div> </div> </div> )} @@ -205,4 +241,6 @@ VehicleJourney.propTypes = { onSelectVehicleJourney: PropTypes.func.isRequired, vehicleJourneys: PropTypes.object.isRequired, allTimeTables: PropTypes.array.isRequired, + allPurchaseWindows: PropTypes.array.isRequired, + extraHeaders: PropTypes.array.isRequired, } diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index 384afba17..ca08ba3b1 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -12,6 +12,7 @@ export default class VehicleJourneys extends Component { this.stopPoints(), this.props.filters.features ) + this.togglePurchaseWindows = this.togglePurchaseWindows.bind(this) this.toggleTimetables = this.toggleTimetables.bind(this) } @@ -49,23 +50,42 @@ export default class VehicleJourneys extends Component { return this.headerManager.showHeader(object_id) } - allTimeTables() { - if(this._allTimeTables){ - return this._allTimeTables - } - let keys = [] + getPurchaseWindowsAndTimeTables(){ + let timetables_keys = [] + let windows_keys = [] this._allTimeTables = [] + this._allPurchaseWindows = [] this.vehicleJourneysList().map((vj, index) => { vj.time_tables.map((tt, _) => { - if(keys.indexOf(tt.id) < 0){ - keys.push(tt.id) + if(timetables_keys.indexOf(tt.id) < 0){ + timetables_keys.push(tt.id) this._allTimeTables.push(tt) } }) + vj.purchase_windows.map((tt, _) => { + if(windows_keys.indexOf(tt.id) < 0){ + windows_keys.push(tt.id) + this._allPurchaseWindows.push(tt) + } + }) }) + } + + allTimeTables() { + if(! this._allTimeTables){ + this.getPurchaseWindowsAndTimeTables() + } return this._allTimeTables } + allPurchaseWindows() { + if(!this._allPurchaseWindows){ + this.getPurchaseWindowsAndTimeTables() + } + + return this._allPurchaseWindows + } + toggleTimetables(e) { $('.table-2entries .detailed-timetables').toggleClass('hidden') $('.table-2entries .detailed-timetables-bt').toggleClass('active') @@ -74,6 +94,14 @@ export default class VehicleJourneys extends Component { false } + togglePurchaseWindows(e) { + $('.table-2entries .detailed-purchase-windows').toggleClass('hidden') + $('.table-2entries .detailed-purchase-windows-bt').toggleClass('active') + this.componentDidUpdate() + e.preventDefault() + false + } + componentDidUpdate(prevProps, prevState) { if(this.props.status.isFetching == false){ $('.table-2entries').each(function() { @@ -127,10 +155,29 @@ export default class VehicleJourneys extends Component { ) } + purchaseWindowURL(tt) { + let refURL = window.location.pathname.split('/', 3).join('/') + let ttURL = refURL + '/purchase_windows/' + tt.id + return ( + <a href={ttURL} title='Voir le calendrier commercial'><span className='fa fa-calendar' style={{color: (tt.color ? tt.color : '#4B4B4B')}}></span>{tt.name}</a> + ) + } + + extraHeaderLabel(header) { + if(header["type"] == "custom_field"){ + return this.props.customFields[header["name"]]["name"] + } + else{ + return I18n.attribute_name("vehicle_journey", header) + } + } + render() { this.previousBreakpoint = undefined this._allTimeTables = null + this._allPurchaseWindows = null let detailed_calendars = this.hasFeature('detailed_calendars') && !this.isReturn() && (this.allTimeTables().length > 0) + let detailed_purchase_windows = this.hasFeature('detailed_purchase_windows') && !this.isReturn() && (this.allPurchaseWindows().length > 0) if(this.props.status.isFetching == true) { return ( <div className="isLoading" style={{marginTop: 80, marginBottom: 80}}> @@ -170,17 +217,44 @@ export default class VehicleJourneys extends Component { <div>{I18n.attribute_name("vehicle_journey", "name")}</div> <div>{I18n.attribute_name("vehicle_journey", "journey_pattern_id")}</div> <div>{I18n.model_name("company")}</div> - { this.hasFeature('purchase_windows') && <div>{I18n.model_name("purchase_window", "plural": true)}</div> } + { + this.props.extraHeaders.map((header, i) => + <div key={i}>{this.extraHeaderLabel(header)}</div> + ) + } + { this.hasFeature('purchase_windows') && + <div> + { detailed_purchase_windows && + <a href='#' onClick={this.togglePurchaseWindows} className='detailed-purchase-windows-bt'> + <span className='fa fa-angle-up'></span> + {I18n.model_name("purchase_window", {"plural": true})} + </a> + } + { !detailed_purchase_windows && I18n.model_name("purchase_window", {"plural": true})} + </div> + } + { detailed_purchase_windows && + <div className="detailed-purchase-windows hidden"> + {this.allPurchaseWindows().map((tt, i)=> + <div key={i}> + <p> + {this.purchaseWindowURL(tt)} + </p> + <p>{tt.bounding_dates.join(' > ')}</p> + </div> + )} + </div> + } <div> { detailed_calendars && <a href='#' onClick={this.toggleTimetables} className='detailed-timetables-bt'> <span className='fa fa-angle-up'></span> - {I18n.model_name("time_table", "plural": true)} + {I18n.model_name("time_table", {"plural": true})} </a> } - { !detailed_calendars && I18n.model_name("time_table", "plural": true)} + { !detailed_calendars && I18n.model_name("time_table", {"plural": true})} </div> - { !this.isReturn() && + { detailed_calendars && <div className="detailed-timetables hidden"> {this.allTimeTables().map((tt, i)=> <div key={i}> @@ -217,6 +291,8 @@ export default class VehicleJourneys extends Component { vehicleJourneys={this} disabled={this.isReturn()} allTimeTables={this.allTimeTables()} + allPurchaseWindows={this.allPurchaseWindows()} + extraHeaders={this.props.extraHeaders} /> )} </div> @@ -232,6 +308,8 @@ export default class VehicleJourneys extends Component { VehicleJourneys.propTypes = { status: PropTypes.object.isRequired, filters: PropTypes.object.isRequired, + extraHeaders: PropTypes.array.isRequired, + customFields: PropTypes.object.isRequired, stopPointsList: PropTypes.array.isRequired, onLoadFirstPage: PropTypes.func.isRequired, onUpdateTime: PropTypes.func.isRequired, diff --git a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js index 76d1c3a78..f21e2c87e 100644 --- a/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js +++ b/app/javascript/vehicle_journeys/containers/VehicleJourneysList.js @@ -10,7 +10,9 @@ const mapStateToProps = (state) => { status: state.status, filters: state.filters, stopPointsList: state.stopPointsList, - returnStopPointsList: state.returnStopPointsList + returnStopPointsList: state.returnStopPointsList, + extraHeaders: window.extra_headers, + customFields: window.custom_fields, } } @@ -20,8 +22,8 @@ const mapDispatchToProps = (dispatch) => { dispatch(actions.fetchingApi()) actions.fetchVehicleJourneys(dispatch, undefined, undefined, filters.queryString, routeUrl) }, - onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled) => { - dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled)) + onUpdateTime: (e, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency=false) => { + dispatch(actions.updateTime(e.target.value, subIndex, index, timeUnit, isDeparture, isArrivalsToggled, enforceConsistency)) }, onSelectVehicleJourney: (index) => { dispatch(actions.selectVehicleJourney(index)) diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index e7f68761e..6524f8d6f 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -153,11 +153,11 @@ const vehicleJourney= (state = {}, action, keep) => { newSchedule.departure_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) if(!action.isArrivalsToggled) newSchedule.arrival_time[action.timeUnit] = newSchedule.departure_time[action.timeUnit] - newSchedule = actions.adjustSchedule(action, newSchedule) + newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) }else{ newSchedule.arrival_time[action.timeUnit] = actions.pad(action.val, action.timeUnit) - newSchedule = actions.adjustSchedule(action, newSchedule) + newSchedule = actions.adjustSchedule(action, newSchedule, action.enforceConsistency) return _.assign({}, state.vehicle_journey_at_stops[action.subIndex], {arrival_time: newSchedule.arrival_time, departure_time: newSchedule.departure_time, delta: newSchedule.delta}) } }else{ diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 157390a21..4c8014780 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -39,6 +39,13 @@ module Chouette self.slice(*attrs).values + ranges_attrs end + def bounding_dates + [ + date_ranges.map(&:first).min, + date_ranges.map(&:last).max, + ] + end + # def checksum_attributes # end diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index f58f97eee..1918c90d1 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -46,6 +46,11 @@ module Chouette validates_numericality_of :waiting_time, greater_than_or_equal_to: 0, only_integer: true, if: :waiting_time validate :parent_area_type_must_be_greater validate :area_type_of_right_kind + validate :registration_number_is_set + + before_validation do + self.registration_number ||= self.stop_area_referential.generate_registration_number + end def self.nullable_attributes [:registration_number, :street_name, :country_code, :fare_code, @@ -73,6 +78,22 @@ module Chouette end end + def registration_number_is_set + return unless self.stop_area_referential.registration_number_format.present? + if self.stop_area_referential.stop_areas.where(registration_number: self.registration_number).\ + where.not(id: self.id).exists? + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.already_taken')) + end + + unless self.registration_number.present? + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.cannot_be_empty')) + end + + unless self.stop_area_referential.validates_registration_number(self.registration_number) + errors.add(:registration_number, I18n.t('stop_areas.errors.registration_number.invalid')) + end + end + after_update :clean_invalid_access_links before_save :coordinates_to_lat_lng @@ -362,29 +383,55 @@ module Chouette end def activated? - deleted_at.nil? + deleted_at.nil? && confirmed_at end def deactivated? - !activated? + deleted_at && confirmed_at.nil? end def activate + self.confirmed_at = Time.now self.deleted_at = nil end def deactivate + self.confirmed_at = nil self.deleted_at = Time.now end def activate! + update_attribute :confirmed_at, Time.now update_attribute :deleted_at, nil end def deactivate! + update_attribute :confirmed_at, nil update_attribute :deleted_at, Time.now end + def status + return :deleted if deleted_at + return :confirmed if confirmed_at + + :in_creation + end + + def status=(status) + case status&.to_sym + when :deleted + deactivate + when :confirmed + activate + when :in_creation + self.confirmed_at = self.deleted_at = nil + end + end + + def self.statuses + %i{in_creation confirmed deleted} + end + def time_zone_offset return 0 unless time_zone.present? ActiveSupport::TimeZone[time_zone]&.utc_offset diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 46522c354..b3987060a 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -62,7 +62,7 @@ module Chouette scope :with_ordered_stop_area_ids, ->(first, second){ if first.present? && second.present? joins(journey_pattern: :stop_points). - joins('INNER JOIN "journey_patterns" ON "journey_patterns"."id" = "vehicle_journeys"."journey_pattern_id" INNER JOIN "journey_patterns_stop_points" ON "journey_patterns_stop_points"."journey_pattern_id" = "journey_patterns"."id" INNER JOIN "stop_points" as "second_stop_points" ON "stop_points"."id" = "journey_patterns_stop_points"."stop_point_id"'). + joins('INNER JOIN "journey_patterns" ON "journey_patterns"."id" = "vehicle_journeys"."journey_pattern_id" INNER JOIN "journey_patterns_stop_points" ON "journey_patterns_stop_points"."journey_pattern_id" = "journey_patterns"."id" INNER JOIN "stop_points" as "second_stop_points" ON "second_stop_points"."id" = "journey_patterns_stop_points"."stop_point_id"'). where('stop_points.stop_area_id = ?', first). where('second_stop_points.stop_area_id = ? and stop_points.position < second_stop_points.position', second) else diff --git a/app/models/chouette/vehicle_journey_at_stop.rb b/app/models/chouette/vehicle_journey_at_stop.rb index d875442ee..3f5bd5abf 100644 --- a/app/models/chouette/vehicle_journey_at_stop.rb +++ b/app/models/chouette/vehicle_journey_at_stop.rb @@ -4,8 +4,10 @@ module Chouette include Chouette::ForAlightingEnumerations include ChecksumSupport - DAY_OFFSET_MAX = 1 + DAY_OFFSET_MAX = 2 + @@day_offset_max = DAY_OFFSET_MAX + mattr_accessor :day_offset_max belongs_to :stop_point belongs_to :vehicle_journey @@ -40,7 +42,7 @@ module Chouette I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', short_id: vehicle_journey&.get_objectid&.short_id, - max: DAY_OFFSET_MAX + 1 + max: Chouette::VehicleJourneyAtStop.day_offset_max + 1 ) ) end @@ -51,7 +53,7 @@ module Chouette I18n.t( 'vehicle_journey_at_stops.errors.day_offset_must_not_exceed_max', short_id: vehicle_journey&.get_objectid&.short_id, - max: DAY_OFFSET_MAX + 1 + max: Chouette::VehicleJourneyAtStop.day_offset_max + 1 ) ) end @@ -62,7 +64,7 @@ module Chouette # nil offsets. Handle these gracefully by forcing them to a 0 offset. offset ||= 0 - offset < 0 || offset > DAY_OFFSET_MAX + offset < 0 || offset > Chouette::VehicleJourneyAtStop.day_offset_max end def checksum_attributes @@ -82,12 +84,12 @@ module Chouette format_time arrival_time.utc end - def departure_local_time - local_time departure_time + def departure_local_time offset=nil + local_time departure_time, offset end - def arrival_local_time - local_time arrival_time + def arrival_local_time offset=nil + local_time arrival_time, offset end def departure_local @@ -98,12 +100,15 @@ module Chouette format_time arrival_local_time end + def time_zone_offset + return 0 unless stop_point&.stop_area&.time_zone.present? + ActiveSupport::TimeZone[stop_point.stop_area.time_zone]&.utc_offset || 0 + end + private - def local_time time - return unless time - return time unless stop_point&.stop_area&.time_zone.present? - return time unless ActiveSupport::TimeZone[stop_point.stop_area.time_zone].present? - time + ActiveSupport::TimeZone[stop_point.stop_area.time_zone].utc_offset + def local_time time, offset=nil + return nil unless time + time + (offset || time_zone_offset) end def format_time time diff --git a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb index 7497cd72c..cfa0e8bfc 100644 --- a/app/models/chouette/vehicle_journey_at_stops_day_offset.rb +++ b/app/models/chouette/vehicle_journey_at_stops_day_offset.rb @@ -4,31 +4,32 @@ module Chouette @at_stops = at_stops end - def calculate! - arrival_offset = 0 - departure_offset = 0 + def time_from_fake_date fake_date + fake_date - fake_date.to_date.to_time + end + def calculate! + offset = 0 + tz_offset = @at_stops.first&.time_zone_offset @at_stops.inject(nil) do |prior_stop, stop| next stop if prior_stop.nil? # we only compare time of the day, not actual times - stop_arrival_time = stop.arrival_time - stop.arrival_time.to_date.to_time - stop_departure_time = stop.departure_time - stop.departure_time.to_date.to_time - prior_stop_arrival_time = prior_stop.arrival_time - prior_stop.arrival_time.to_date.to_time - prior_stop_departure_time = prior_stop.departure_time - prior_stop.departure_time.to_date.to_time - - if stop_arrival_time < prior_stop_departure_time || - stop_arrival_time < prior_stop_arrival_time - arrival_offset += 1 + stop_arrival_time = time_from_fake_date stop.arrival_local_time(tz_offset) + stop_departure_time = time_from_fake_date stop.departure_local_time(tz_offset) + prior_stop_departure_time = time_from_fake_date prior_stop.departure_local_time(tz_offset) + + if stop_arrival_time < prior_stop_departure_time + offset += 1 end - if stop_departure_time < stop_arrival_time || - stop_departure_time < prior_stop_departure_time - departure_offset += 1 + stop.arrival_day_offset = offset + + if stop_departure_time < stop_arrival_time + offset += 1 end - stop.arrival_day_offset = arrival_offset - stop.departure_day_offset = departure_offset + stop.departure_day_offset = offset stop end diff --git a/app/models/concerns/iev_interfaces/message.rb b/app/models/concerns/iev_interfaces/message.rb new file mode 100644 index 000000000..ad41e98b7 --- /dev/null +++ b/app/models/concerns/iev_interfaces/message.rb @@ -0,0 +1,9 @@ +module IevInterfaces::Message + extend ActiveSupport::Concern + + included do + extend Enumerize + enumerize :criticity, in: %i(info warning error) + validates :criticity, presence: true + end +end diff --git a/app/models/concerns/iev_interfaces/resource.rb b/app/models/concerns/iev_interfaces/resource.rb new file mode 100644 index 000000000..7f8c3eefd --- /dev/null +++ b/app/models/concerns/iev_interfaces/resource.rb @@ -0,0 +1,9 @@ +module IevInterfaces::Resource + extend ActiveSupport::Concern + + included do + extend Enumerize + enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true + validates_presence_of :name, :resource_type, :reference + end +end diff --git a/app/models/concerns/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb new file mode 100644 index 000000000..fdd976f39 --- /dev/null +++ b/app/models/concerns/iev_interfaces/task.rb @@ -0,0 +1,119 @@ +module IevInterfaces::Task + extend ActiveSupport::Concern + + included do + belongs_to :parent, polymorphic: true + belongs_to :workbench, class_name: "::Workbench" + belongs_to :referential + + mount_uploader :file, ImportUploader + + has_many :children, foreign_key: :parent_id, class_name: self.name, dependent: :destroy + + extend Enumerize + enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new + + validates :name, presence: true + validates_presence_of :workbench, :creator + + has_many :messages, class_name: messages_class_name, dependent: :destroy, foreign_key: "#{messages_class_name.split('::').first.downcase}_id" + has_many :resources, class_name: resources_class_name, dependent: :destroy, foreign_key: "#{resources_class_name.split('::').first.downcase}_id" + + scope :where_started_at_in, ->(period_range) do + where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end) + end + + scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } + + before_create :initialize_fields + after_save :notify_parent + end + + module ClassMethods + def launched_statuses + %w(new pending) + end + + def failed_statuses + %w(failed aborted canceled) + end + + def finished_statuses + %w(successful failed warning aborted canceled) + end + + def abort_old + where( + 'created_at < ? AND status NOT IN (?)', + 4.hours.ago, + finished_statuses + ).update_all(status: 'aborted') + end + end + + def notify_parent + return unless parent.present? + return unless status_changed? + parent.child_change + t = Time.now + self.notified_parent_at = t + self.class.where(id: self.id).update_all notified_parent_at: t + end + + def children_succeedeed + children.with_status(:successful, :warning).count + end + + def update_status + status = + if children.where(status: self.class.failed_statuses).count > 0 + 'failed' + elsif children.where(status: "warning").count > 0 + 'warning' + elsif children.where(status: "successful").count == children.count + 'successful' + else + 'running' + end + + attributes = { + current_step: children.count, + status: status + } + + if self.class.finished_statuses.include?(status) + attributes[:ended_at] = Time.now + end + + update attributes + end + + def child_change + return if self.class.finished_statuses.include?(status) + update_status + end + + def call_iev_callback + return if self.class.finished_statuses.include?(status) + threaded_call_boiv_iev + end + + private + + def threaded_call_boiv_iev + Thread.new(&method(:call_boiv_iev)) + end + + def call_boiv_iev + Rails.logger.error("Begin IEV call for import") + Net::HTTP.get iev_callback_url + Rails.logger.error("End IEV call for import") + rescue Exception => e + logger.error "IEV server error : #{e.message}" + logger.error e.backtrace.inspect + end + + private + def initialize_fields + end +end diff --git a/app/models/export.rb b/app/models/export.rb deleted file mode 100644 index 8c38d6684..000000000 --- a/app/models/export.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Export - include JobConcern - - def initialize( response ) - @datas = response - end - - def report? - links["action_report"].present? - end - - def report - Rails.cache.fetch("#{cache_key}/action_report", expires_in: cache_expiration) do - report_path = links["action_report"] - if report_path - response = Ievkit.get(report_path) - ExportReport.new(response) - else - nil - end - end - end - - def destroy - delete_path = links["delete"] - cancel_path = links["cancel"] - - if delete_path - Ievkit.delete(delete_path) - elsif cancel_path - Ievkit.delete(cancel_path) - else - nil - end - end - - def file_path? - links["data"].present? - end - - def file_path - links["data"] - end - - def filename - File.basename(file_path) if file_path - end - - def filename_extension - File.extname(filename).gsub(".", "") if filename - end - -end diff --git a/app/models/export/base.rb b/app/models/export/base.rb new file mode 100644 index 000000000..6085e0ffb --- /dev/null +++ b/app/models/export/base.rb @@ -0,0 +1,89 @@ +class Export::Base < ActiveRecord::Base + self.table_name = "exports" + + validates :type, presence: true + + def self.messages_class_name + "Export::Message" + end + + def self.resources_class_name + "Export::Resource" + end + + def self.human_name + I18n.t("export.#{self.name.demodulize.underscore}") + end + + def self.file_extension_whitelist + %w(zip csv json) + end + + if Rails.env.development? + def self.force_load_descendants + path = Rails.root.join 'app/models/export' + Dir.chdir path do + Dir['**/*.rb'].each do |src| + next if src =~ /^base/ + klass_name = "Export::#{src[0..-4].camelize}" + Rails.logger.info "Loading #{klass_name}" + begin + klass_name.constantize + rescue => e + Rails.logger.info "Failed: #{e.message}" + nil + end + end + end + end + end + + def self.option name, opts={} + store_accessor :options, name + if !!opts[:required] + validates name, presence: true + end + @options ||= {} + @options[name] = opts + end + + def self.options + @options ||= {} + end + + include IevInterfaces::Task + + def self.model_name + ActiveModel::Name.new Export::Base, Export::Base, "Export" + end + + def self.user_visible_descendants + descendants.select &:user_visible? + end + + def self.user_visible? + true + end + + def visible_options + options.select{|k, v| ! k.match /^_/} + end + + def display_option_value option_name, context + option = self.class.options[option_name.to_sym] + val = self.options[option_name.to_s] + if option[:display] + context.instance_exec(val, &option[:display]) + else + val + end + end + + private + + def initialize_fields + super + self.token_upload = SecureRandom.urlsafe_base64 + end + +end diff --git a/app/models/export/message.rb b/app/models/export/message.rb new file mode 100644 index 000000000..b64b524ac --- /dev/null +++ b/app/models/export/message.rb @@ -0,0 +1,8 @@ +class Export::Message < ActiveRecord::Base + self.table_name = :export_messages + + include IevInterfaces::Message + + belongs_to :export, class_name: Export::Base + belongs_to :resource, class_name: Export::Resource +end diff --git a/app/models/export/netex.rb b/app/models/export/netex.rb new file mode 100644 index 000000000..069ec2209 --- /dev/null +++ b/app/models/export/netex.rb @@ -0,0 +1,22 @@ +class Export::Netex < Export::Base + after_commit :call_iev_callback, on: :create + option :export_type, collection: %w(line full), required: true + option :duration, type: :integer, default_value: 90, required: true + option :line_code + + private + + def iev_callback_url + URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/exporter/new?id=#{id}") + end + + # def self.user_visible? + # false + # end + + def destroy_non_ready_referential + if referential && !referential.ready + referential.destroy + end + end +end diff --git a/app/models/export/referential_companies.rb b/app/models/export/referential_companies.rb new file mode 100644 index 000000000..0b6187060 --- /dev/null +++ b/app/models/export/referential_companies.rb @@ -0,0 +1,92 @@ +class Export::ReferentialCompanies < Export::Base + option :referential_id, + type: :select, + collection: ->(){workbench.referentials.all}, + required: true, + display: ->(val){r = Referential.find(val); link_to(r.name, [r])} + + after_create :call_exporter_async + + def referential + Referential.find referential_id + end + + def call_exporter_async + SimpleExportWorker.perform_async(id) + end + + def exporter + SimpleExporter.define :referential_companies do |config| + config.separator = ";" + config.encoding = 'ISO-8859-1' + config.add_column :name + config.add_column :registration_number + end + + @exporter ||= begin + if options[:_exporter_id] + exporter = SimpleExporter.find options[:exporter_id] + else + exporter = SimpleExporter.create configuration_name: :referential_companies + options[:_exporter_id] = exporter.id + end + exporter + end + end + + def call_exporter + tmp = Tempfile.new ["referential_companies", ".csv"] + referential.switch + exporter.configure do |config| + config.collection = referential.companies.order(:name) + end + exporter.filepath = tmp.path + exporter.export + set_status_from_exporter + convert_exporter_journal_to_messages + self.file = tmp + self.save! + end + + def set_status_from_exporter + if exporter.status == :error + self.status = :failed + else + if exporter.status == :success + self.status = :successful + else + self.status = :warning + end + end + end + + def convert_exporter_journal_to_messages + self.messages.destroy_all + exporter.journal.each do |journal_item| + journal_item.symbolize_keys! + vals = {} + + if journal_item[:kind].to_s == "warning" + vals[:criticity] = :warning + elsif journal_item[:kind].to_s == "error" + vals[:criticity] = :error + else + vals[:criticity] = :info + if journal_item[:event].to_s == "success" + vals[:message_key] = :success + end + end + vals[:resource_attributes] = journal_item[:row] + + if journal_item[:message].present? + vals[:message_key] = :full_text + vals[:message_attributes] = { + text: journal_item[:message] + } + end + vals[:message_attributes] ||= {} + vals[:message_attributes][:line] = journal_item[:line] + self.messages.build vals + end + end +end diff --git a/app/models/export/resource.rb b/app/models/export/resource.rb new file mode 100644 index 000000000..98f103be4 --- /dev/null +++ b/app/models/export/resource.rb @@ -0,0 +1,8 @@ +class Export::Resource < ActiveRecord::Base + self.table_name = :export_resources + + include IevInterfaces::Resource + + belongs_to :export, class_name: Export::Base + has_many :messages, class_name: "ExportMessage", foreign_key: :resource_id +end diff --git a/app/models/export/workgroup.rb b/app/models/export/workgroup.rb new file mode 100644 index 000000000..3430596c7 --- /dev/null +++ b/app/models/export/workgroup.rb @@ -0,0 +1,9 @@ +class Export::Workgroup < Export::Base + after_commit :launch_worker, :on => :create + + option :duration, required: true, type: :integer, default_value: 90 + + def launch_worker + WorkgroupExportWorker.perform_async(id) + end +end diff --git a/app/models/export_log_message.rb b/app/models/export_log_message.rb deleted file mode 100644 index 4bb9d3cc7..000000000 --- a/app/models/export_log_message.rb +++ /dev/null @@ -1,42 +0,0 @@ -class ExportLogMessage < ActiveRecord::Base - belongs_to :export - - acts_as_list :scope => :export - - validates_presence_of :key - validates_inclusion_of :severity, :in => %w{info warning error ok uncheck fatal} - - def arguments=(arguments) - write_attribute :arguments, (arguments.to_json if arguments.present?) - end - - def arguments - @decoded_arguments ||= - begin - if (stored_arguments = raw_attributes).present? - ActiveSupport::JSON.decode stored_arguments - else - {} - end - end - end - - def raw_attributes - read_attribute(:arguments) - end - - before_validation :define_default_attributes, :on => :create - def define_default_attributes - self.severity ||= "info" - end - - def full_message - last_key=key.rpartition("|").last - begin - I18n.translate last_key, arguments.symbolize_keys.merge(:scope => "export_log_messages.messages").merge(:default => :undefined).merge(:key => last_key) - rescue => e - Rails.logger.error "missing arguments for message "+last_key - I18n.translate "WRONG_DATA",{"0"=>last_key}.symbolize_keys.merge(:scope => "export_log_messages.messages").merge(:default => :undefined).merge(:key => "WRONG_DATA") - end - end -end diff --git a/app/models/export_report.rb b/app/models/export_report.rb deleted file mode 100644 index 3c0788106..000000000 --- a/app/models/export_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ExportReport - #include ReportConcern - - def initialize( response ) - @datas = response.action_report - end - -end diff --git a/app/models/export_service.rb b/app/models/export_service.rb deleted file mode 100644 index 2dbe0d7b3..000000000 --- a/app/models/export_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ExportService - - attr_reader :referential - - def initialize(referential) - @referential = referential - end - - # Find an export whith his id - def find(id) - Export.new( Ievkit.scheduled_job(referential.slug, id, { :action => "exporter" }) ) - end - - # Find all exports - def all - [].tap do |jobs| - Ievkit.jobs(referential.slug, { :action => "exporter" }).each do |job| - jobs << Export.new( job ) - end - end - end - -end diff --git a/app/models/export_task.rb b/app/models/export_task.rb deleted file mode 100644 index f02cb914e..000000000 --- a/app/models/export_task.rb +++ /dev/null @@ -1,119 +0,0 @@ -class ExportTask - extend Enumerize - extend ActiveModel::Naming - extend ActiveModel::Translation - extend ActiveModel::Callbacks - include ActiveModel::Validations - include ActiveModel::Conversion - - attr_accessor :start_date, :end_date - - define_model_callbacks :initialize, only: :after - - enumerize :data_format, in: %w( neptune netex gtfs hub kml ) - attr_accessor :referential_id, :user_id, :user_name, :references_type, :data_format, :name, :projection_type, :reference_ids - - validates_presence_of :referential_id - validates_presence_of :user_id - validates_presence_of :user_name - validates_presence_of :name - validates_presence_of :data_format - - validate :period_validation - - after_initialize :init_period - - def initialize( params = {} ) - run_callbacks :initialize do - params.each {|k,v| send("#{k}=",v)} - end - end - - def period_validation - st_date = start_date.is_a?(String) ? Date.parse(start_date) : start_date - ed_date = end_date.is_a?(String) ? Date.parse(end_date) : end_date - - unless Chouette::TimeTable.start_validity_period.nil? || st_date.nil? - tt_st_date = Chouette::TimeTable.start_validity_period - errors.add(:start_date, ExportTask.human_attribute_name("start_date_greater_than" , {:tt_st_date => tt_st_date})) unless tt_st_date <= st_date - end - unless st_date.nil? || ed_date.nil? - errors.add(:end_date, ExportTask.human_attribute_name("end_date_greater_than_start_date")) unless st_date <= ed_date - end - unless ed_date.nil? || Chouette::TimeTable.end_validity_period.nil? - tt_ed_date = Chouette::TimeTable.end_validity_period - errors.add(:end_date, ExportTask.human_attribute_name("end_date_less_than", {:tt_ed_date => tt_ed_date})) unless ed_date <= tt_ed_date - end - end - - def init_period - unless Chouette::TimeTable.start_validity_period.nil? - if start_date.nil? - self.start_date = Chouette::TimeTable.start_validity_period - end - if end_date.nil? - self.end_date = Chouette::TimeTable.end_validity_period - end - end - end - - def referential - Referential.find(referential_id) - end - - def organisation - referential.organisation - end - - def save - if self.valid? - # Call Iev Server - begin - Ievkit.create_job( referential.slug, "exporter", data_format, { - :file1 => params_io, - } ) - rescue Exception => exception - raise exception - end - true - else - false - end - end - - def self.data_formats - self.data_format.values - end - - def self.references_types - self.references_type.values - end - - def params - {}.tap do |h| - h["parameters"] = action_params - end - end - - def action_params - {} - end - - def params_io - file = StringIO.new( params.to_json ) - Faraday::UploadIO.new(file, "application/json", "parameters.json") - end - - def self.optional_attributes(references_type) - [] - end - - def optional_attributes - self.class.optional_attributes(references_type.to_s) - end - - def optional_attribute?(attribute) - optional_attributes.include? attribute.to_sym - end - -end diff --git a/app/models/gtfs_export.rb b/app/models/gtfs_export.rb deleted file mode 100644 index d0b9fc4f9..000000000 --- a/app/models/gtfs_export.rb +++ /dev/null @@ -1,47 +0,0 @@ -class GtfsExport < ExportTask - - validates_presence_of :time_zone, unless: Proc.new { |e| e.optional_attribute? :time_zone } - attr_accessor :object_id_prefix, :time_zone - - enumerize :references_type, in: %w( network line company group_of_line stop_area ) - - after_initialize :init_params - - def init_params - if time_zone.nil? - self.time_zone = "Paris" - end - end - - def real_time_zone - ActiveSupport::TimeZone.find_tzinfo(time_zone).name - end - - def action_params - { - "gtfs-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "time_zone" => real_time_zone, - "object_id_prefix" => object_id_prefix, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def self.optional_attributes(references_type) - super.tap do |optional_attributes| - optional_attributes.push :time_zone, :start_date, :end_date if references_type == "stop_area" - end - end - - def data_format - "gtfs" - end - -end diff --git a/app/models/hub_export.rb b/app/models/hub_export.rb deleted file mode 100644 index 802600692..000000000 --- a/app/models/hub_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class HubExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "hub-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "hub" - end - -end diff --git a/app/models/import.rb b/app/models/import.rb deleted file mode 100644 index 29aadcd56..000000000 --- a/app/models/import.rb +++ /dev/null @@ -1,103 +0,0 @@ -class Import < ActiveRecord::Base - mount_uploader :file, ImportUploader - belongs_to :workbench - belongs_to :referential - - belongs_to :parent, polymorphic: true - - has_many :messages, class_name: "ImportMessage", dependent: :destroy - has_many :resources, class_name: "ImportResource", dependent: :destroy - has_many :children, foreign_key: :parent_id, class_name: "Import", dependent: :destroy - - scope :where_started_at_in, ->(period_range) do - where('started_at BETWEEN :begin AND :end', begin: period_range.begin, end: period_range.end) - end - - scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } - - extend Enumerize - enumerize :status, in: %w(new pending successful warning failed running aborted canceled), scope: true, default: :new - - validates :name, presence: true - validates :file, presence: true - validates_presence_of :workbench, :creator - - before_create :initialize_fields - - def self.model_name - ActiveModel::Name.new Import, Import, "Import" - end - - def children_succeedeed - children.with_status(:successful, :warning).count - end - - def self.launched_statuses - %w(new pending) - end - - def self.failed_statuses - %w(failed aborted canceled) - end - - def self.finished_statuses - %w(successful failed warning aborted canceled) - end - - def self.abort_old - where( - 'created_at < ? AND status NOT IN (?)', - 4.hours.ago, - finished_statuses - ).update_all(status: 'aborted') - end - - def notify_parent - parent.child_change - update(notified_parent_at: DateTime.now) - end - - def child_change - return if self.class.finished_statuses.include?(status) - - update_status - update_referentials - end - - def update_status - status = - if children.where(status: self.class.failed_statuses).count > 0 - 'failed' - elsif children.where(status: "warning").count > 0 - 'warning' - elsif children.where(status: "successful").count == children.count - 'successful' - end - - attributes = { - current_step: children.count, - status: status - } - - if self.class.finished_statuses.include?(status) - attributes[:ended_at] = Time.now - end - - update attributes - end - - def update_referentials - return unless self.class.finished_statuses.include?(status) - - children.each do |import| - import.referential.update(ready: true) if import.referential - end - end - - private - - def initialize_fields - self.token_download = SecureRandom.urlsafe_base64 - end - -end diff --git a/app/models/import/base.rb b/app/models/import/base.rb new file mode 100644 index 000000000..1dd9c4195 --- /dev/null +++ b/app/models/import/base.rb @@ -0,0 +1,45 @@ +class Import::Base < ActiveRecord::Base + self.table_name = "imports" + validates :file, presence: true + + def self.messages_class_name + "Import::Message" + end + + def self.resources_class_name + "Import::Resource" + end + + def self.file_extension_whitelist + %w(zip) + end + + include IevInterfaces::Task + + def self.model_name + ActiveModel::Name.new Import::Base, Import::Base, "Import" + end + + def child_change + return if self.class.finished_statuses.include?(status) + + super + update_referentials + end + + def update_referentials + return unless self.class.finished_statuses.include?(status) + + children.each do |import| + import.referential.update(ready: true) if import.referential + end + end + + private + + def initialize_fields + super + self.token_download = SecureRandom.urlsafe_base64 + end + +end diff --git a/app/models/gtfs_import.rb b/app/models/import/gtfs.rb index d09ca4cb3..03cf49e60 100644 --- a/app/models/gtfs_import.rb +++ b/app/models/import/gtfs.rb @@ -1,5 +1,5 @@ require 'net/http' -class GtfsImport < Import +class Import::Gtfs < Import::Base before_destroy :destroy_non_ready_referential after_commit :launch_java_import, on: :create diff --git a/app/models/import/message.rb b/app/models/import/message.rb new file mode 100644 index 000000000..c1900a718 --- /dev/null +++ b/app/models/import/message.rb @@ -0,0 +1,8 @@ +class Import::Message < ActiveRecord::Base + self.table_name = :import_messages + + include IevInterfaces::Message + + belongs_to :import, class_name: Import::Base + belongs_to :resource, class_name: Import::Resource +end diff --git a/app/models/import_message_export.rb b/app/models/import/message_export.rb index 991eb0f61..7a7add08c 100644 --- a/app/models/import_message_export.rb +++ b/app/models/import/message_export.rb @@ -2,7 +2,7 @@ require "csv" require "zip" -class ImportMessageExport +class Import::MessageExport include ActiveModel::Validations include ActiveModel::Conversion extend ActiveModel::Naming @@ -29,7 +29,7 @@ class ImportMessageExport csv_string = CSV.generate(options) do |csv| csv << column_names import_messages.each do |import_message| - csv << [import_message.criticity, import_message.message_key, I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number") ] + csv << [import_message.criticity, import_message.message_attributes['test_id'], I18n.t("import_messages.#{import_message.message_key}", import_message.message_attributes.deep_symbolize_keys), *import_message.resource_attributes.values_at("filename", "line_number", "column_number") ] end end # We add a BOM to indicate we use UTF-8 diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb new file mode 100644 index 000000000..2b0982229 --- /dev/null +++ b/app/models/import/netex.rb @@ -0,0 +1,24 @@ +require 'net/http' +class Import::Netex < Import::Base + before_destroy :destroy_non_ready_referential + + after_commit :call_iev_callback, on: :create + + before_save def abort_unless_referential + self.status = 'aborted' unless referential + end + + validates_presence_of :parent + + private + + def iev_callback_url + URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}") + end + + def destroy_non_ready_referential + if referential && !referential.ready + referential.destroy + end + end +end diff --git a/app/models/import/resource.rb b/app/models/import/resource.rb new file mode 100644 index 000000000..5bd011039 --- /dev/null +++ b/app/models/import/resource.rb @@ -0,0 +1,8 @@ +class Import::Resource < ActiveRecord::Base + self.table_name = :import_resources + + include IevInterfaces::Resource + + belongs_to :import, class_name: Import::Base + has_many :messages, class_name: "Import::Message", foreign_key: :resource_id +end diff --git a/app/models/workbench_import.rb b/app/models/import/workbench.rb index 27f53a44f..f6e15cb89 100644 --- a/app/models/workbench_import.rb +++ b/app/models/import/workbench.rb @@ -1,4 +1,4 @@ -class WorkbenchImport < Import +class Import::Workbench < Import::Base after_commit :launch_worker, :on => :create def launch_worker diff --git a/app/models/import_message.rb b/app/models/import_message.rb deleted file mode 100644 index de70c35d1..000000000 --- a/app/models/import_message.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ImportMessage < ActiveRecord::Base - extend Enumerize - belongs_to :import - belongs_to :resource, class_name: ImportResource - enumerize :criticity, in: %i(info warning error) - - validates :criticity, presence: true -end diff --git a/app/models/import_report.rb b/app/models/import_report.rb deleted file mode 100644 index ba13f0118..000000000 --- a/app/models/import_report.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ImportReport - #include ReportConcern - - def initialize( response ) - @datas = response.action_report - end - -end diff --git a/app/models/import_resource.rb b/app/models/import_resource.rb deleted file mode 100644 index 55e752e74..000000000 --- a/app/models/import_resource.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ImportResource < ActiveRecord::Base - belongs_to :import - - extend Enumerize - enumerize :status, in: %i(OK ERROR WARNING IGNORED), scope: true - - validates_presence_of :name, :resource_type, :reference - - has_many :messages, class_name: "ImportMessage", foreign_key: :resource_id - -end diff --git a/app/models/import_service.rb b/app/models/import_service.rb deleted file mode 100644 index 2e3c1012b..000000000 --- a/app/models/import_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -class ImportService - - attr_reader :referential - - def initialize( referential ) - @referential = referential - end - - # Find an import whith his id - def find(id) - Import.new( Ievkit.scheduled_job(referential.slug, id, { :action => "importer" }) ) - end - - # Find all imports - def all - [].tap do |jobs| - Ievkit.jobs(referential.slug, { :action => "importer" }).each do |job| - jobs << Import.new( job ) - end - end - end - -end diff --git a/app/models/import_task.rb b/app/models/import_task.rb deleted file mode 100644 index 7dfa2c644..000000000 --- a/app/models/import_task.rb +++ /dev/null @@ -1,141 +0,0 @@ -require "zip" - -class ImportTask - extend Enumerize - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Conversion - - # TODO : Move in configuration - @@root = "#{Rails.root}/tmp/imports" - cattr_accessor :root - - enumerize :data_format, in: %w( neptune netex gtfs ) - attr_accessor :referential_id, :user_id, :user_name, :data_format, :resources, :name, :no_save - - validates_presence_of :referential_id - validates_presence_of :resources - validates_presence_of :user_id - validates_presence_of :user_name - validates_presence_of :name - - validate :validate_file_size, :validate_file_content - - def initialize( params = {} ) - params.each {|k,v| send("#{k}=",v)} - end - - def referential - Referential.find(referential_id) - end - - def organisation - referential.organisation - end - - def save - if valid? - # Save resources - save_resources - - # Call Iev Server - begin - Ievkit.create_job(referential.slug, "importer", data_format, { - :file1 => params_io, - :file2 => transport_data_io - } - - ) - - # Delete resources - delete_resources - rescue Exception => exception - # If iev server has an error must delete resources before - delete_resources - - raise exception - end - true - else - false - end - end - - def params - {}.tap do |h| - h["parameters"] = {} - end - end - - def self.data_formats - self.data_format.values - end - - def params_io - file = StringIO.new( params.to_json ) - Faraday::UploadIO.new(file, "application/json", "parameters.json") - end - - def transport_data_io - file = File.new(saved_resources_path, "r") - if file_extname == ".zip" - Faraday::UploadIO.new(file, "application/zip", original_filename ) - elsif file_extname == ".xml" - Faraday::UploadIO.new(file, "application/xml", original_filename ) - end - end - - def save_resources - FileUtils.mkdir_p root - FileUtils.cp resources.path, saved_resources_path - end - - def delete_resources - FileUtils.rm saved_resources_path if File.exists? saved_resources_path - end - - def original_filename - resources.original_filename - end - - def file_extname - File.extname(original_filename) if original_filename - end - - def saved_resources_path - @saved_resources_path ||= "#{root}/#{Time.now.to_i}#{file_extname}" - end - - @@maximum_file_size = 80.megabytes - cattr_accessor :maximum_file_size - - def validate_file_size - return unless resources.present? and resources.path.present? and File.exists? resources.path - - if File.size(resources.path) > maximum_file_size - message = I18n.t("activemodel.errors.models.import_task.attributes.resources.maximum_file_size", file_size: ActionController::Base.helpers.number_to_human_size(File.size(resources.path)), maximum_file_size: ActionController::Base.helpers.number_to_human_size(maximum_file_size)) - errors.add(:resources, message) - end - end - - @@valid_mime_types = { - neptune: %w{application/zip application/xml}, - netex: %w{application/zip}, - gtfs: %w{application/zip text/plain} - } - cattr_accessor :valid_mime_types - - def validate_file_content - return unless resources.present? and resources.path.present? and File.exists? resources.path - - mime_type = (File.open(resources.path) { |f| MimeMagic.by_magic f }).try :type - expected_mime_types = valid_mime_types[data_format.to_sym] - - unless expected_mime_types.include? mime_type - message = I18n.t("activemodel.errors.models.import_task.attributes.resources.invalid_mime_type", mime_type: mime_type) - errors.add(:resources, message) - end - end - -end diff --git a/app/models/kml_export.rb b/app/models/kml_export.rb deleted file mode 100644 index f6db77172..000000000 --- a/app/models/kml_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class KmlExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "kml-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "kml" - end - -end diff --git a/app/models/line_referential.rb b/app/models/line_referential.rb index 15b2f6276..0d2ed39b1 100644 --- a/app/models/line_referential.rb +++ b/app/models/line_referential.rb @@ -10,6 +10,7 @@ class LineReferential < ActiveRecord::Base has_many :networks, class_name: 'Chouette::Network' has_many :line_referential_syncs, -> { order created_at: :desc } has_many :workbenches + has_one :workgroup def add_member(organisation, options = {}) attributes = options.merge organisation: organisation diff --git a/app/models/merge.rb b/app/models/merge.rb index d42d882ac..e72c794fe 100644 --- a/app/models/merge.rb +++ b/app/models/merge.rb @@ -159,7 +159,6 @@ class Merge < ActiveRecord::Base route_id: nil, objectid: objectid, ) - new_route.stop_points.build attributes end diff --git a/app/models/neptune_export.rb b/app/models/neptune_export.rb deleted file mode 100644 index f25db69c0..000000000 --- a/app/models/neptune_export.rb +++ /dev/null @@ -1,27 +0,0 @@ -class NeptuneExport < ExportTask - - attr_accessor :extensions, :export_type - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "neptune-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "projection_type" => projection_type || "", - "add_extension" => extensions, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "neptune" - end - -end diff --git a/app/models/neptune_import.rb b/app/models/neptune_import.rb deleted file mode 100644 index 1f0bdaa13..000000000 --- a/app/models/neptune_import.rb +++ /dev/null @@ -1,19 +0,0 @@ -class NeptuneImport < ImportTask - - def action_params - { - "neptune-import" => { - "no_save" => no_save, - "user_name" => user_name, - "name" => name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - } - } - end - - def data_format - "neptune" - end - -end diff --git a/app/models/netex_export.rb b/app/models/netex_export.rb deleted file mode 100644 index a4c3e2454..000000000 --- a/app/models/netex_export.rb +++ /dev/null @@ -1,24 +0,0 @@ -class NetexExport < ExportTask - - enumerize :references_type, in: %w( network line company group_of_line ) - - def action_params - { - "netex-export" => { - "name" => name, - "references_type" => references_type, - "reference_ids" => reference_ids, - "user_name" => user_name, - "organisation_name" => organisation.name, - "referential_name" => referential.name, - "start_date" => start_date, - "end_date" => end_date - } - } - end - - def data_format - "netex" - end - -end diff --git a/app/models/netex_import.rb b/app/models/netex_import.rb deleted file mode 100644 index b21af3408..000000000 --- a/app/models/netex_import.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'net/http' -class NetexImport < Import - before_destroy :destroy_non_ready_referential - - after_commit :launch_java_import, on: :create - before_save def abort_unless_referential - self.status = 'aborted' unless referential - end - - validates_presence_of :parent - - def launch_java_import - return if self.class.finished_statuses.include?(status) - threaded_call_boiv_iev - end - - private - - def destroy_non_ready_referential - if referential && !referential.ready - referential.destroy - end - end - - def threaded_call_boiv_iev - Thread.new(&method(:call_boiv_iev)) - end - - def call_boiv_iev - Rails.logger.error("Begin IEV call for import") - Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}")) - Rails.logger.error("End IEV call for import") - rescue Exception => e - logger.error "IEV server error : #{e.message}" - logger.error e.backtrace.inspect - end - -end diff --git a/app/models/simple_exporter.rb b/app/models/simple_exporter.rb index c1ba75d0a..c267b5b8c 100644 --- a/app/models/simple_exporter.rb +++ b/app/models/simple_exporter.rb @@ -16,19 +16,14 @@ class SimpleExporter < SimpleInterface @statuses = "" - if ENV["NO_TRANSACTION"] - process_collection - else - ActiveRecord::Base.transaction do - process_collection - end - end + process_collection + self.status ||= :success rescue SimpleInterface::FailedOperation self.status = :failed ensure @csv&.close - self.save! + task_finished end def collection @@ -46,14 +41,13 @@ class SimpleExporter < SimpleInterface protected def init_env opts @number_of_lines = collection.size - super opts end def process_collection self.configuration.before_actions(:all).each do |action| action.call self end - log "Starting export ...", color: :green - log "Export will be written in #{filepath}", color: :green + log "Starting export ..." + log "Export will be written in #{filepath}" @csv << self.configuration.columns.map(&:name) if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id") ids = collection.pluck :id @@ -87,6 +81,7 @@ class SimpleExporter < SimpleInterface if val.nil? && !col.omit_nil? push_in_journal({event: :attribute_not_found, message: "Value missing for: #{[col.scope, col.attribute].flatten.join('.')}", kind: :warning}) self.status ||= :success_with_warnings + @new_status ||= colorize("✓", :orange) end if val.nil? && col.required? @@ -137,6 +132,10 @@ class SimpleExporter < SimpleInterface flatten_hash(v).map do |h_k, h_v| h["#{k}.#{h_k}".to_sym] = h_v end + elsif v.is_a? ActiveRecord::Base + flatten_hash(v.attributes).map do |h_k, h_v| + h["#{k}.#{h_k}".to_sym] = h_v + end else h[k] = v end diff --git a/app/models/simple_importer.rb b/app/models/simple_importer.rb index 6f0b8d7a8..4cfe90cff 100644 --- a/app/models/simple_importer.rb +++ b/app/models/simple_importer.rb @@ -33,10 +33,11 @@ class SimpleImporter < SimpleInterface rescue SimpleInterface::FailedOperation self.status = :failed ensure - self.save! + task_finished end def encode_string s + return if s.nil? s.encode("utf-8").force_encoding("utf-8") end @@ -80,6 +81,7 @@ class SimpleImporter < SimpleInterface if self.configuration.ignore_failures unless @current_record.save @new_status = colorize("x", :red) + self.status = :success_with_errors push_in_journal({message: "errors: #{@current_record.errors.messages}", error: "invalid record", event: :error, kind: :error}) end else diff --git a/app/models/simple_interface.rb b/app/models/simple_interface.rb index 489419482..43c740b57 100644 --- a/app/models/simple_interface.rb +++ b/app/models/simple_interface.rb @@ -1,5 +1,5 @@ class SimpleInterface < ActiveRecord::Base - attr_accessor :configuration + attr_accessor :configuration, :interfaces_group class << self def configuration_class @@ -16,7 +16,7 @@ class SimpleInterface < ActiveRecord::Base def find_configuration name @importers ||= {} configuration = @importers[name.to_sym] - raise "Importer not found: #{name}" unless configuration + raise "#{self.name} not found: #{name}" unless configuration configuration end end @@ -27,14 +27,21 @@ class SimpleInterface < ActiveRecord::Base self.journal ||= [] end + def configuration + @configuration ||= self.class.find_configuration self.configuration_name + end + def init_env opts @verbose = opts.delete :verbose - @errors = [] + @_errors = [] @messages = [] @padding = 1 @current_line = -1 + @number_of_lines ||= 1 @padding = [1, Math.log([@number_of_lines, 1].max, 10).ceil()].max + @output_dir = opts[:output_dir] || Rails.root.join('tmp', self.class.name.tableize) + @start_time = Time.now end def configure @@ -55,6 +62,7 @@ class SimpleInterface < ActiveRecord::Base custom_print "\nFAILED: \n errors: #{msg}\n exception: #{e.message}\n#{e.backtrace.join("\n")}", color: :red unless self.configuration.ignore_failures push_in_journal({message: msg, error: e.message, event: :error, kind: :error}) @new_status = colorize("x", :red) + self.status = :success_with_errors if self.configuration.ignore_failures raise SimpleInterface::FailedRow if opts[:abort_row] else @@ -66,27 +74,66 @@ class SimpleInterface < ActiveRecord::Base def log msg, opts={} msg = msg.to_s msg = colorize msg, opts[:color] if opts[:color] + @start_time ||= Time.now + time = Time.now - @start_time + @messages ||= [] if opts[:append] - @messages[-1] = (@messages[-1] || "") + msg + _time, _msg = @messages.pop || [] + _time ||= time + _msg ||= "" + @messages.push [_time, _msg+msg] + elsif opts[:replace] + @messages.pop + @messages << [time, msg] else - @messages << msg + @messages << [time, msg] + end + print_state true + end + + def output_filepath + @output_filepath ||= File.join @output_dir, "#{self.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" + end + + def write_output_to_csv + cols = %i(line kind event message error) + if self.journal.size > 0 && self.journal.first[:row].present? + log "Writing output log" + FileUtils.mkdir_p @output_dir + keys = self.journal.first[:row].map(&:first) + CSV.open(output_filepath, "w") do |csv| + csv << cols + keys + self.journal.each do |j| + csv << cols.map{|c| j[c]} + j[:row].map(&:last) + end + end + log "Output written in #{output_filepath}", replace: true end - print_state end protected + def task_finished + log "Saving..." + self.save! + log "Saved", replace: true + write_output_to_csv + log "FINISHED, status: " + log status, color: SimpleInterface.status_color(status), append: true + print_state true + end + def push_in_journal data line = (@current_line || 0) + 1 line += 1 if configuration.headers - @errors ||= [] + @_errors ||= [] self.journal.push data.update(line: line, row: @current_row) if data[:kind] == :error || data[:kind] == :warning - @errors.push data + @_errors.push data end end - def colorize txt, color + def self.colorize txt, color color = { red: "31", green: "32", @@ -95,12 +142,25 @@ class SimpleInterface < ActiveRecord::Base "\e[#{color}m#{txt}\e[0m" end - def print_state + def self.status_color status + color = :green + color = :orange if status.to_s == "success_with_warnings" + color = :red if status.to_s == "success_with_errors" + color = :red if status.to_s == "error" + color + end + + def colorize txt, color + SimpleInterface.colorize txt, color + end + + def print_state force=false return unless @verbose + return if !@last_repaint.nil? && (Time.now - @last_repaint < 0.1) && !force @status_width ||= begin - term_width = %x(tput cols).to_i - term_width - @padding - 10 + @term_width = %x(tput cols).to_i + @term_width - @padding - 10 rescue 100 end @@ -112,12 +172,24 @@ class SimpleInterface < ActiveRecord::Base 50 end + msg = "" + + if @banner.nil? && interfaces_group.present? + @banner = interfaces_group.banner @status_width + @status_height -= @banner.lines.count + 2 + end + + if @banner.present? + msg += @banner + msg += "\n" + "-"*@term_width + "\n" + end + full_status = @statuses || "" full_status = full_status.last(@status_width*10) || "" padding_size = [(@number_of_lines - @current_line - 1), (@status_width - full_status.size/10)].min full_status = "#{full_status}#{"."*[padding_size, 0].max}" - msg = "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" + msg += "#{"%#{@padding}d" % (@current_line + 1)}/#{@number_of_lines}: #{full_status}" lines_count = [(@status_height / 2) - 3, 1].max @@ -125,15 +197,17 @@ class SimpleInterface < ActiveRecord::Base msg += "\n\n" msg += colorize "=== MESSAGES (#{@messages.count}) ===\n", :green msg += "[...]\n" if @messages.count > lines_count - msg += @messages.last(lines_count).map{|m| m.truncate(@status_width)}.join("\n") + msg += @messages.last(lines_count).map do |m| + "[#{"%.5f" % m[0]}]\t" + m[1].truncate(@status_width - 10) + end.join("\n") msg += "\n"*[lines_count-@messages.count, 0].max end - if @errors.any? + if @_errors.any? msg += "\n\n" - msg += colorize "=== ERRORS (#{@errors.count}) ===\n", :red - msg += "[...]\n" if @errors.count > lines_count - msg += @errors.last(lines_count).map do |j| + msg += colorize "=== ERRORS (#{@_errors.count}) ===\n", :red + msg += "[...]\n" if @_errors.count > lines_count + msg += @_errors.last(lines_count).map do |j| kind = j[:kind] kind = colorize(kind, kind == :error ? :red : :orange) kind = "[#{kind}]" @@ -142,6 +216,7 @@ class SimpleInterface < ActiveRecord::Base end.join("\n") end custom_print msg, clear: true + @last_repaint = Time.now end def custom_print msg, opts={} diff --git a/app/models/simple_interfaces_group.rb b/app/models/simple_interfaces_group.rb new file mode 100644 index 000000000..808be6570 --- /dev/null +++ b/app/models/simple_interfaces_group.rb @@ -0,0 +1,76 @@ +class SimpleInterfacesGroup + attr_accessor :name, :shared_options + + def initialize name + @name = name + @interfaces = [] + @current_step = 0 + end + + def add_interface interface, name, action, opts={} + @interfaces.push({interface: interface, name: name, action: action, opts: opts}) + end + + def run + @interfaces.each do |interface_def| + interface = interface_def[:interface] + interface.interfaces_group = self + interface.send interface_def[:action], interface_def[:opts].reverse_update(shared_options || {}) + return if interface.status == :error + @current_step += 1 + end + + print_summary + end + + def banner width=nil + width ||= @width + width ||= 128 + @width = width + + name = "### #{self.name} ###" + centered_name = " " * ([width - name.size, 0].max / 2) + name + banner = [centered_name, ""] + banner << @interfaces.each_with_index.map do |interface, i| + if interface[:interface].status.present? + SimpleInterface.colorize interface[:name], SimpleInterface.status_color(interface[:interface].status) + elsif i == @current_step + "☕︎ #{interface[:name]}" + else + interface[:name] + end + end.join(' > ') + banner.join("\n") + end + + def print_summary + puts "\e[H\e[2J" + out = [banner] + out << "-" * @width + out << "" + out << SimpleInterface.colorize("=== STATUSES ===", :green) + out << "" + @interfaces.each do |i| + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{SimpleInterface.colorize i[:interface].status, SimpleInterface.status_color(i[:interface].status)}" + end + out << "" + out << SimpleInterface.colorize("=== OUTPUTS ===", :green) + out << "" + @interfaces.each do |i| + if i[:interface].is_a? SimpleExporter + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{i[:interface].filepath}" + end + end + out << "" + out << "" + out << SimpleInterface.colorize("=== DEBUG OUTPUTS ===", :green) + out << "" + @interfaces.each do |i| + out << "#{i[:name].rjust(@interfaces.map{|i| i[:name].size}.max)}:\t#{i[:interface].output_filepath}" + end + out << "" + out << "" + + print out.join("\n") + end +end diff --git a/app/models/simple_json_exporter.rb b/app/models/simple_json_exporter.rb index 8c44c149a..024d75c97 100644 --- a/app/models/simple_json_exporter.rb +++ b/app/models/simple_json_exporter.rb @@ -21,22 +21,18 @@ class SimpleJsonExporter < SimpleExporter @statuses = "" - if ENV["NO_TRANSACTION"] - process_collection - else - ActiveRecord::Base.transaction do - process_collection - end - end + process_collection self.status ||= :success rescue SimpleInterface::FailedOperation self.status = :failed ensure if @file + log "Writing to JSON file..." @file.write @out.to_json + log "JSON file written", replace: true @file.close end - self.save! + task_finished end protected @@ -50,20 +46,22 @@ class SimpleJsonExporter < SimpleExporter def process_collection self.configuration.before_actions(:all).each do |action| action.call self end - log "Starting export ...", color: :green - log "Export will be written in #{filepath}", color: :green + log "Starting export ..." + log "Export will be written in #{filepath}" if collection.is_a?(ActiveRecord::Relation) && collection.model.column_names.include?("id") + log "Using paginated collection", color: :green ids = collection.pluck :id ids.in_groups_of(configuration.batch_size).each do |batch_ids| - collection.where(id: batch_ids).each do |item| + collection.where(id: batch_ids.compact).each do |item| handle_item item end end else + log "Using non-paginated collection", color: :orange collection.each{|item| handle_item item } end - print_state + print_state true end def resolve_node item, node @@ -96,7 +94,7 @@ class SimpleJsonExporter < SimpleExporter map_item_to_rows(item).each_with_index do |item, i| @number_of_lines = number_of_lines + i serialized_item = {} - @current_row = item.attributes + @current_row = item.attributes.symbolize_keys @current_row = @current_row.slice(*configuration.logged_attributes) if configuration.logged_attributes.present? @new_status = nil diff --git a/app/models/stop_area_referential.rb b/app/models/stop_area_referential.rb index 54e895cd0..a9d3cc9b1 100644 --- a/app/models/stop_area_referential.rb +++ b/app/models/stop_area_referential.rb @@ -1,4 +1,6 @@ class StopAreaReferential < ActiveRecord::Base + validates :registration_number_format, format: { with: /\AX*\z/ } + include ObjectidFormatterSupport has_many :stop_area_referential_memberships has_many :organisations, through: :stop_area_referential_memberships @@ -6,6 +8,7 @@ class StopAreaReferential < ActiveRecord::Base has_many :stop_areas, class_name: 'Chouette::StopArea' has_many :stop_area_referential_syncs, -> {order created_at: :desc} has_many :workbenches + has_one :workgroup def add_member(organisation, options = {}) attributes = options.merge organisation: organisation @@ -15,4 +18,30 @@ class StopAreaReferential < ActiveRecord::Base def last_sync stop_area_referential_syncs.last end + + def generate_registration_number + return "" unless registration_number_format.present? + last = self.stop_areas.order("registration_number DESC NULLS LAST").limit(1).first&.registration_number + if self.stop_areas.count == 26**self.registration_number_format.size + raise "NO MORE AVAILABLE VALUES FOR registration_number in referential #{self.name}" + end + + return "A" * self.registration_number_format.size unless last + + if last == "Z" * self.registration_number_format.size + val = "A" * self.registration_number_format.size + while self.stop_areas.where(registration_number: val).exists? + val = val.next + end + val + else + last.next + end + end + + def validates_registration_number value + return false unless value.size == registration_number_format.size + return false unless value =~ /^[A-Z]*$/ + true + end end diff --git a/app/models/workbench.rb b/app/models/workbench.rb index b6f90c7dc..b5f4673bb 100644 --- a/app/models/workbench.rb +++ b/app/models/workbench.rb @@ -13,8 +13,9 @@ class Workbench < ActiveRecord::Base has_many :companies, through: :line_referential has_many :group_of_lines, through: :line_referential has_many :stop_areas, through: :stop_area_referential - has_many :imports - has_many :workbench_imports + has_many :imports, class_name: Import::Base + has_many :exports, class_name: Export::Base + has_many :workbench_imports, class_name: Import::Workbench has_many :compliance_check_sets has_many :compliance_control_sets has_many :merges diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb index 3af20ae23..708225a2a 100644 --- a/app/models/workgroup.rb +++ b/app/models/workgroup.rb @@ -11,6 +11,8 @@ class Workgroup < ActiveRecord::Base validates_presence_of :line_referential_id validates_presence_of :stop_area_referential_id + validates_uniqueness_of :stop_area_referential_id + validates_uniqueness_of :line_referential_id has_many :custom_fields diff --git a/app/policies/export_policy.rb b/app/policies/export_policy.rb new file mode 100644 index 000000000..e667f3207 --- /dev/null +++ b/app/policies/export_policy.rb @@ -0,0 +1,15 @@ +class ExportPolicy < ApplicationPolicy + class Scope < Scope + def resolve + scope + end + end + + def create? + user.has_permission?('exports.create') + end + + def update? + user.has_permission?('exports.update') + end +end diff --git a/app/uploaders/import_uploader.rb b/app/uploaders/import_uploader.rb index 60e17ca0f..3491768f6 100644 --- a/app/uploaders/import_uploader.rb +++ b/app/uploaders/import_uploader.rb @@ -37,7 +37,7 @@ class ImportUploader < CarrierWave::Uploader::Base # Add a white list of extensions which are allowed to be uploaded. # For images you might use something like this: def extension_whitelist - %w(zip) + model.class.try(:file_extension_whitelist) || %w(zip) end # Override the filename of the uploaded files: diff --git a/app/views/api/v1/stop_areas/short_description.rabl b/app/views/api/v1/stop_areas/short_description.rabl index 7d571b0a2..2f27f04d8 100644 --- a/app/views/api/v1/stop_areas/short_description.rabl +++ b/app/views/api/v1/stop_areas/short_description.rabl @@ -1,7 +1,7 @@ object @stop_area extends "api/v1/trident_objects/short_description" -[:id, :name, :city_name, :zip_code, :area_type, :longitude, :latitude, :long_lat_type].each do |attr| +[:id, :name, :city_name, :zip_code, :area_type, :kind, :longitude, :latitude, :long_lat_type].each do |attr| attributes attr, :unless => lambda { |m| m.send( attr).nil?} end node(:parent_object_id) do |stop_area| diff --git a/app/views/compliance_checks/show.html.slim b/app/views/compliance_checks/show.html.slim index 8dd699c65..3b3861e0c 100644 --- a/app/views/compliance_checks/show.html.slim +++ b/app/views/compliance_checks/show.html.slim @@ -10,4 +10,4 @@ - if resource.compliance_check_block = definition_list t('compliance_controls.show.metadatas.compliance_check_block'), I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{resource.compliance_check_block.transport_mode}"), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") + I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => resource.compliance_check_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{resource.compliance_check_block.transport_submode}") diff --git a/app/views/compliance_control_sets/_filters.html.slim b/app/views/compliance_control_sets/_filters.html.slim index 4348defac..5cf282559 100644 --- a/app/views/compliance_control_sets/_filters.html.slim +++ b/app/views/compliance_control_sets/_filters.html.slim @@ -11,7 +11,7 @@ = f.input :organisation_name_eq_any, collection: organisations_filters_values, as: :check_boxes, label: false, label_method: lambda {|w| ("<span>#{w.name}</span>").html_safe}, required: false, wrapper_html: {class: 'checkbox_list'} .form-group.togglable class=filter_item_class(params[:q], :updated_at) - = f.label Import.human_attribute_name(:updated_at), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:updated_at), required: false, class: 'control-label' .filter_menu = f.simple_fields_for :updated_at do |p| = p.input :start_date, as: :date, label: false, wrapper_html: {class: 'date smart_date filter_menu-item'}, default: @begin_range, include_blank: @begin_range ? false : true diff --git a/app/views/compliance_controls/show.html.slim b/app/views/compliance_controls/show.html.slim index 6e7a45d12..643237676 100644 --- a/app/views/compliance_controls/show.html.slim +++ b/app/views/compliance_controls/show.html.slim @@ -10,4 +10,4 @@ - if @compliance_control.compliance_control_block = definition_list t('compliance_controls.show.metadatas.compliance_control_block'), I18n.t('activerecord.attributes.compliance_control_blocks.transport_mode') => I18n.t("enumerize.transport_mode.#{@compliance_control.compliance_control_block.transport_mode}"), - I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => I18n.t("enumerize.transport_submode.#{@compliance_control.compliance_control_block.transport_submode}") + I18n.t('activerecord.attributes.compliance_control_blocks.transport_submode') => @compliance_control.compliance_control_block.transport_submode.empty? ? I18n.t("enumerize.transport_submode.undefined") : I18n.t("enumerize.transport_submode.#{@compliance_control.compliance_control_block.transport_submode}") diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index f93b85cad..4adf335d2 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -28,7 +28,7 @@ - if workbench.calendars.present? .list-group - workbench.calendars.order("updated_at desc").limit(5).each do |calendar| - = link_to calendar.name, workgroup_calendars_path(workbench.workgroup, calendar), class: 'list-group-item' + = link_to calendar.name, workgroup_calendar_path(workbench.workgroup, calendar), class: 'list-group-item' - else .panel-body em.small.text-muted diff --git a/app/views/exports/_export.html.slim b/app/views/exports/_export.html.slim deleted file mode 100644 index f1f7e9753..000000000 --- a/app/views/exports/_export.html.slim +++ /dev/null @@ -1,20 +0,0 @@ -#index_item.panel.panel-default.export - .panel-heading - .panel-title.clearfix - span.pull-right - = link_to referential_export_path(@referential, export.id), :method => :delete, :data => {:confirm => t('exports.actions.destroy_confirm')}, class: 'btn btn-danger btn-sm' do - span.fa.fa-trash-o - - h5 - = link_to( referential_export_path(@referential, export.id), :class => "preview", :title => "#{Export.model_name.human.capitalize} #{export.name}") do - = job_status_title(export) - - .panel-body - p - = link_to( font_awesome_classic_tag("fa-file-#{export.filename_extension}-o") + t("exports.show.exported_file"), exported_file_referential_export_path(@referential, export.id) ) if export.file_path - - .panel-footer - = export_attributes_tag(export) - .history - = l export.created_at, :format => "%d/%m/%Y %H:%M" - = " | #{export.user_name}"
\ No newline at end of file diff --git a/app/views/exports/_exports.html.slim b/app/views/exports/_exports.html.slim deleted file mode 100644 index 7a0461def..000000000 --- a/app/views/exports/_exports.html.slim +++ /dev/null @@ -1,9 +0,0 @@ -.page_info - span.search = t("will_paginate.page_entries_info.search") - = page_entries_info @exports - -.exports.paginated_content - = paginated_content @exports, "exports/export" - -.pagination - = will_paginate @exports, :container => false, renderer: RemoteBootstrapPaginationLinkRenderer
\ No newline at end of file diff --git a/app/views/exports/_form.html.slim b/app/views/exports/_form.html.slim new file mode 100644 index 000000000..7817fdf1a --- /dev/null +++ b/app/views/exports/_form.html.slim @@ -0,0 +1,16 @@ += simple_form_for export, as: :export, url: workbench_exports_path(workbench), html: {class: 'form-horizontal', id: 'wb_export_form'}, wrapper: :horizontal_form do |form| + + .row + .col-lg-12 + = form.input :name + .col-lg-12 + = form.input :type, as: :select, collection: Export::Base.user_visible_descendants, label_method: :human_name + + - Export::Base.user_visible_descendants.each do |child| + .slave data-master="[name='export[type]']" data-value=child.name + - child.options.each do |attr, option_def| + = export_option_input form, export, attr, option_def, child + + = form.button :submit, t('actions.submit'), class: 'btn btn-default formSubmitr', form: 'wb_export_form' + += javascript_pack_tag "exports/new" diff --git a/app/views/exports/index.html.slim b/app/views/exports/index.html.slim index bbcb2a5a7..f97b07231 100644 --- a/app/views/exports/index.html.slim +++ b/app/views/exports/index.html.slim @@ -1,10 +1,44 @@ -= title_tag t('.title') +- breadcrumb :exports, @workbench -.warning = t('.warning') +.page_content + .container-fluid + - if params[:q].present? or collection.any? + .row + .col-lg-12 + = render 'shared/iev_interfaces/filters' -#exports = render 'exports' + - if collection.any? + .row + .col-lg-12 + = table_builder_2 collection, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| export_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |export| \ + workbench_export_path(@workbench, export) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + cls: 'table has-search' -- content_for :sidebar do - ul.actions - li - = link_to t('exports.actions.new'), new_referential_export_task_path(@referential), class: 'add'
\ No newline at end of file + = new_pagination collection, 'pull-right' + + - unless collection.any? + .row.mt-xs + .col-lg-12 + = replacement_msg t('exports.search_no_results') + += javascript_pack_tag 'date_filters' diff --git a/app/views/exports/index.js.slim b/app/views/exports/index.js.slim deleted file mode 100644 index b9a413b1b..000000000 --- a/app/views/exports/index.js.slim +++ /dev/null @@ -1 +0,0 @@ -| $('#exports').html("#{escape_javascript(render('exports'))}");
\ No newline at end of file diff --git a/app/views/exports/new.html.slim b/app/views/exports/new.html.slim new file mode 100644 index 000000000..f62386caf --- /dev/null +++ b/app/views/exports/new.html.slim @@ -0,0 +1,7 @@ +- breadcrumb :exports, @workbench + +.page_content + .container-fluid + .row + .col-lg-8.col-lg-offset-2.col-md-8.col-md-offset-2.col-sm-10.col-sm-offset-1 + = render 'form', export: @export, workbench: @workbench diff --git a/app/views/exports/show.html.slim b/app/views/exports/show.html.slim index 1631e0e7e..2a7d7583c 100644 --- a/app/views/exports/show.html.slim +++ b/app/views/exports/show.html.slim @@ -1,26 +1,49 @@ -.title.row - .col-md-8 - = title_tag job_status_title(@export) +- breadcrumb :export, @workbench, @export - .col-md-4 - = export_attributes_tag(@export) +- page_header_content_for @export -- if @export.report.failure_code? - .alert.alert-danger - = t("iev.failure.#{@export.report.failure_code}") +.page_content + .container-fluid + .row + .col-lg-6.col-md-6.col-sm-12.col-xs-12 + - metadatas = { I18n.t("activerecord.attributes.export.type") => @export.object.class.human_name } + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.status") => export_status(@export.status)}) + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.parent") => link_to(@export.parent.name, [@export.parent.workbench, @export.parent])}) if @export.parent.present? + - metadatas = metadatas.update Hash[*@export.visible_options.map{|k, v| [t("activerecord.attributes.export.#{@export.object.class.name.demodulize.underscore}.#{k}"), @export.display_option_value(k, self)]}.flatten] + - metadatas = metadatas.update({I18n.t("activerecord.attributes.export.file") => (@export.file.present? ? link_to(t("actions.download"), @export.file.url) : "-")}) + = definition_list t('metadatas'), metadatas -.progress_bars - = progress_bar_tag(@export) + .row + .col-lg-12 + .error_messages + = render 'shared/iev_interfaces/messages', messages: @export.messages -.export_show - .links - = link_to( font_awesome_classic_tag("fa-file-#{@export.filename_extension}-o") + t("exports.show.exported_file"), exported_file_referential_export_path(@referential, @export.id) ) if @export.file_path - - = render partial: "shared/ie_report.html", locals: { job: @export, line_items: @line_items } + - if @export.children.any? + .row + .col-lg-12 + - coll = @export.children.paginate(page: params[:page] || 1) + = table_builder_2 coll, + [ \ + TableBuilderHelper::Column.new( \ + key: :status, \ + attribute: Proc.new { |n| export_status(n.status) }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :started_at, \ + attribute: Proc.new { |n| l(n.started_at, format: :long) if n.started_at }, \ + ), \ + TableBuilderHelper::Column.new( \ + key: :name, \ + attribute: 'name', \ + link_to: lambda do |export| \ + workbench_export_path(@workbench, export) \ + end \ + ), \ + TableBuilderHelper::Column.new( \ + key: :creator, \ + attribute: 'creator' \ + ) \ + ], + cls: 'table has-search' -- content_for :sidebar do - ul.actions - li - = link_to t('exports.actions.destroy'), referential_export_path(@referential, @export.id), :method => :delete, :data => {:confirm => t('exports.actions.destroy_confirm')}, class: 'remove' - - = history_tag(@export)
\ No newline at end of file + = new_pagination coll, 'pull-right' diff --git a/app/views/imports/_import_messages.html.slim b/app/views/imports/_import_messages.html.slim deleted file mode 100644 index af10b23e5..000000000 --- a/app/views/imports/_import_messages.html.slim +++ /dev/null @@ -1,8 +0,0 @@ -- if import_messages.any? - ul.list-unstyled.import_message-list - - import_messages.each do | import_message | - li - span(class="#{bootstrap_class_for_message_criticity import_message.criticity}") - = t( ['import_messages', - 'compliance_check_messages', - import_message.message_key].join('.'), import_message.message_attributes.symbolize_keys) diff --git a/app/views/imports/index.html.slim b/app/views/imports/index.html.slim index 4fc077bd6..3dff4d80e 100644 --- a/app/views/imports/index.html.slim +++ b/app/views/imports/index.html.slim @@ -2,15 +2,15 @@ .page_content .container-fluid - - if params[:q].present? or @imports.any? + - if params[:q].present? or collection.any? .row .col-lg-12 - = render 'filters' + = render 'shared/iev_interfaces/filters' - if @imports.any? .row .col-lg-12 - = table_builder_2 @imports, + = table_builder_2 collection, [ \ TableBuilderHelper::Column.new( \ key: :status, \ @@ -34,9 +34,9 @@ ], cls: 'table has-search' - = new_pagination @imports, 'pull-right' + = new_pagination collection, 'pull-right' - - unless @imports.any? + - unless collection.any? .row.mt-xs .col-lg-12 = replacement_msg t('imports.search_no_results') diff --git a/app/views/imports/show.html.slim b/app/views/imports/show.html.slim index 7a9d02077..48a4f334c 100644 --- a/app/views/imports/show.html.slim +++ b/app/views/imports/show.html.slim @@ -11,7 +11,7 @@ .row .col-lg-12 .error_messages - = render 'import_messages', import_messages: @import.messages + = render 'shared/iev_interfaces/messages', messages: @import.messages - if @import.children.any? .row diff --git a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim index cb0698cf8..02614dcab 100644 --- a/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim +++ b/app/views/layouts/navigation/_main_nav_left_content_stif.html.slim @@ -29,6 +29,8 @@ span Jeux de données = link_to workbench_imports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'imports') ? 'active' : ''}" do span Import + = link_to workbench_exports_path(current_workbench), class: "list-group-item #{(params[:controller] == 'exports') ? 'active' : ''}" do + span Export = link_to workgroup_calendars_path(current_workbench.workgroup), class: 'list-group-item' do span Modèles de calendrier = link_to workbench_compliance_check_sets_path(current_workbench), class: 'list-group-item' do diff --git a/app/views/layouts/navigation/_page_header.html.slim b/app/views/layouts/navigation/_page_header.html.slim index e407e53da..49f56544b 100644 --- a/app/views/layouts/navigation/_page_header.html.slim +++ b/app/views/layouts/navigation/_page_header.html.slim @@ -1,6 +1,5 @@ - action_links = resource.action_links(params[:action]) rescue nil - action_links ||= decorated_collection.action_links(params[:action]) rescue nil - .page_header .container-fluid .row @@ -13,7 +12,7 @@ h1 = yield :page_header_title - else - if defined?(resource_class) - h1 = t("#{resource_class.model_name.name.underscore.pluralize}.#{params[:action]}.title") + h1 = t("#{resource_class.model_name.name.underscore.gsub('/', '.').pluralize}.#{params[:action]}.title") .col-lg-3.col-md-4.col-sm-5.col-xs-5.text-right .page-action diff --git a/app/views/imports/_filters.html.slim b/app/views/shared/iev_interfaces/_filters.html.slim index 25c0d10d9..9b114c38d 100644 --- a/app/views/imports/_filters.html.slim +++ b/app/views/shared/iev_interfaces/_filters.html.slim @@ -8,11 +8,11 @@ .ffg-row .form-group.togglable class=filter_item_class(params[:q], :status_eq_any) - = f.label Import.human_attribute_name(:status), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:status), required: false, class: 'control-label' = f.input :status_eq_any, collection: %w(pending successful warning failed), as: :check_boxes, label: false, label_method: lambda{|l| ("<span>" + import_status(l) + "</span>").html_safe}, required: false, wrapper_html: { class: "checkbox_list"} .form-group.togglable class=filter_item_class(params[:q], :started_at) - = f.label Import.human_attribute_name(:started_at), required: false, class: 'control-label' + = f.label Import::Base.human_attribute_name(:started_at), required: false, class: 'control-label' .filter_menu = f.simple_fields_for :started_at do |p| = p.input :start_date, as: :date, label: false, wrapper_html: { class: 'date smart_date filter_menu-item' }, default: @begin_range, include_blank: @begin_range ? false : true diff --git a/app/views/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim new file mode 100644 index 000000000..82f1add57 --- /dev/null +++ b/app/views/shared/iev_interfaces/_messages.html.slim @@ -0,0 +1,14 @@ +- if messages.any? + ul.list-unstyled.import_message-list + - messages.order(:created_at).each do | message | + li + .row class=bootstrap_class_for_message_criticity(message.criticity) + - if message.message_attributes["line"] + .col-md-1= "L. #{message.message_attributes["line"]}" + .col-md-5= export_message_content message + - else + .col-md-6= export_message_content message + .col-md-6 + - if message.criticity != "info" + pre + = JSON.pretty_generate message.resource_attributes || {} diff --git a/app/views/stop_areas/_filters.html.slim b/app/views/stop_areas/_filters.html.slim index 00369d3ed..a32638567 100644 --- a/app/views/stop_areas/_filters.html.slim +++ b/app/views/stop_areas/_filters.html.slim @@ -13,6 +13,32 @@ .form-group.togglable class=filter_item_class(params[:q], :area_type_eq_any) = f.label Chouette::StopArea.human_attribute_name(:area_type), required: false, class: 'control-label' = f.input :area_type_eq_any, checked: params[:q] && params[:q][:area_type_eq_any], collection: Chouette::AreaType.options, as: :check_boxes, label: false, label_method: lambda{|w| ("<span>" + w[0] + "</span>").html_safe}, required: false, wrapper_html: { class: 'checkbox_list' } + + .form-group.togglable class=filter_item_class(params[:q], :status) + = f.label Chouette::StopArea.human_attribute_name(:state), required: false, class: 'control-label' + .form-group.checkbox_list + = f.simple_fields_for :status do |p| + = p.input :in_creation, + label: ("<span>#{t('activerecord.attributes.stop_area.in_creation')}<span class='fa fa-pencil text-info'></span></span>").html_safe, + as: :boolean, + wrapper_html: { class: 'checkbox-wrapper' }, + checked_value: true, + unchecked_value: false, + input_html: { checked: @status.try(:[], :in_creation) } + = p.input :confirmed, + label: ("<span>#{t('activerecord.attributes.stop_area.confirmed')}<span class='fa fa-check-circle text-success'></span></span>").html_safe, + as: :boolean, + wrapper_html: { class: 'checkbox-wrapper' }, + checked_value: true, + unchecked_value: false, + input_html: { checked: @status.try(:[], :confirmed) } + = p.input :deactivated, + label: ("<span>#{t('activerecord.attributes.stop_area.deleted')}<span class='fa fa-exclamation-circle text-danger'></span></span>").html_safe, + as: :boolean, + wrapper_html: { class: 'checkbox-wrapper' }, + checked_value: true, + unchecked_value: false, + input_html: { checked: @status.try(:[], :deactivated) } .actions = link_to 'Effacer', @workbench, class: 'btn btn-link' diff --git a/app/views/stop_areas/_form.html.slim b/app/views/stop_areas/_form.html.slim index bb1fbe1e9..d6682ef70 100644 --- a/app/views/stop_areas/_form.html.slim +++ b/app/views/stop_areas/_form.html.slim @@ -27,6 +27,8 @@ .slave data-master="[name='stop_area[kind]']" data-value=kind = f.input :area_type, as: :select, :input_html => {id: kind, :disabled => !@stop_area.new_record?}, :collection => Chouette::AreaType.options(kind), :include_blank => false, disabled: !@stop_area.new_record? + = f.input :status, as: :select, :collection => stop_area_status_options, :include_blank => false + .location_info h3 = t("stop_areas.stop_area.localisation") @@ -48,7 +50,7 @@ - if has_feature?(:stop_area_waiting_time) = f.input :waiting_time, input_html: { min: 0 } - = f.input :registration_number, required: format_restriction_for_locales(@referential) == '.hub', :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.registration_number")} + = f.input :registration_number, required: stop_area_registration_number_is_required(f.object), :input_html => {title: stop_area_registration_number_title(f.object), value: stop_area_registration_number_value(f.object)} = f.input :fare_code = f.input :nearest_topic_name, :input_html => {:title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.nearest_topic_name")} = f.input :comment, as: :text, :input_html => {:rows => 5, :title => t("formtastic.titles#{format_restriction_for_locales(@referential)}.stop_area.comment")} diff --git a/app/views/stop_areas/index.html.slim b/app/views/stop_areas/index.html.slim index 71c7f995c..587efbdaa 100644 --- a/app/views/stop_areas/index.html.slim +++ b/app/views/stop_areas/index.html.slim @@ -32,8 +32,8 @@ attribute: 'registration_number' \ ), \ TableBuilderHelper::Column.new( \ - key: :deleted_at, \ - attribute: Proc.new { |s| line_status(s.deleted_at) } \ + name: t('activerecord.attributes.stop_area.state'), \ + attribute: Proc.new { |s| stop_area_status(s) } \ ), \ TableBuilderHelper::Column.new( \ key: :zip_code, \ diff --git a/app/views/stop_areas/show.html.slim b/app/views/stop_areas/show.html.slim index 34b872e91..a6147b86d 100644 --- a/app/views/stop_areas/show.html.slim +++ b/app/views/stop_areas/show.html.slim @@ -20,7 +20,7 @@ @stop_area.human_attribute_name(:zip_code) => @stop_area.zip_code, @stop_area.human_attribute_name(:city_name) => @stop_area.city_name, @stop_area.human_attribute_name(:country_code) => @stop_area.country_code.presence || '-', - t('activerecord.attributes.stop_area.state') => (@stop_area.deleted_at ? t('stop_areas.show.state.deactivated') : t('stop_areas.show.state.active')), + t('activerecord.attributes.stop_area.state') => stop_area_status(@stop_area), @stop_area.human_attribute_name(:comment) => @stop_area.try(:comment), }) = definition_list t('metadatas'), attributes diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index d53d8b50c..d23c61394 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -29,6 +29,7 @@ | window.features = #{raw @features}; | window.all_missions = #{(@all_missions.to_json).html_safe}; | window.custom_fields = #{(@custom_fields.to_json).html_safe}; + | window.extra_headers = #{(@extra_headers.to_json).html_safe}; // | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe}; - if has_feature?(:vehicle_journeys_return_route) diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index bb26ce797..d218038a6 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -36,7 +36,7 @@ end if has_feature? :purchase_windows child(:purchase_windows, :object_root => false) do |purchase_windows| - attributes :id, :objectid, :name, :color + attributes :id, :objectid, :name, :color, :bounding_dates end end diff --git a/app/workers/simple_export_worker.rb b/app/workers/simple_export_worker.rb new file mode 100644 index 000000000..d41736307 --- /dev/null +++ b/app/workers/simple_export_worker.rb @@ -0,0 +1,10 @@ +class SimpleExportWorker + include Sidekiq::Worker + + def perform(export_id) + export = Export::Base.find(export_id) + export.update(status: 'running', started_at: Time.now) + export.call_exporter + export.update(ended_at: Time.now) + end +end diff --git a/app/workers/workbench_import_worker.rb b/app/workers/workbench_import_worker.rb index 53cbb222a..fd2a888f0 100644 --- a/app/workers/workbench_import_worker.rb +++ b/app/workers/workbench_import_worker.rb @@ -12,7 +12,7 @@ class WorkbenchImportWorker def perform(import_id) @entries = 0 - @workbench_import ||= WorkbenchImport.find(import_id) + @workbench_import ||= Import::Workbench.find(import_id) workbench_import.update(status: 'running', started_at: Time.now) zip_service = ZipService.new(downloaded, allowed_lines) @@ -44,7 +44,6 @@ class WorkbenchImportWorker raise end - def upload_entry_group entry, element_count update_object_state entry, element_count.succ return unless entry.ok? @@ -80,7 +79,6 @@ class WorkbenchImportWorker File.unlink(eg_file.path) end - # Queries # ======= diff --git a/app/workers/workgroup_export_worker.rb b/app/workers/workgroup_export_worker.rb new file mode 100644 index 000000000..29493cea6 --- /dev/null +++ b/app/workers/workgroup_export_worker.rb @@ -0,0 +1,38 @@ +class WorkgroupExportWorker + include Sidekiq::Worker + + attr_reader :workbench_export + + # Workers + # ======= + + def perform(export_id) + @entries = 0 + @workbench_export ||= Export::Workgroup.find(export_id) + + workbench_export.update(status: 'running', started_at: Time.now) + create_sub_jobs + rescue Exception => e + logger.error e.message + workbench_export.update( status: 'failed' ) + raise + end + + def create_sub_jobs + # XXX TO DO + workbench_export.workbench.workgroup.referentials.each do |ref| + ref.lines.each do |line| + netex_export = Export::Netex.new + netex_export.name = "Export line #{line.name} of Referential #{ref.name}" + netex_export.workbench = workbench_export.workbench + netex_export.creator = workbench_export.creator + netex_export.export_type = :line + netex_export.duration = workbench_export.duration + netex_export.line_code = line.objectid + netex_export.parent = workbench_export + netex_export.save! + end + end + end + +end |
