diff options
202 files changed, 2714 insertions, 1989 deletions
| diff --git a/Gemfile.lock b/Gemfile.lock index 090886f3d..251de0e1a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -332,7 +332,7 @@ GEM      net-ssh (4.1.0)      net-ssh-gateway (2.0.0)        net-ssh (>= 4.0.0) -    newrelic_rpm (4.0.0.332) +    newrelic_rpm (4.8.0.341)      nokogiri (1.8.2)        mini_portile2 (~> 2.3.0)      open4 (1.3.4) diff --git a/app/assets/fonts/sBoiv/sboiv.eot b/app/assets/fonts/sBoiv/sboiv.eotBinary files differ index 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.ttfBinary files differ index 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.woffBinary files differ index 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 ef19bd538..ba51f7de7 100644 --- a/app/assets/stylesheets/components/_tables.sass +++ b/app/assets/stylesheets/components/_tables.sass @@ -376,6 +376,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 ff6f2f36f..907707670 100644 --- a/app/helpers/table_builder_helper/column.rb +++ b/app/helpers/table_builder_helper/column.rb @@ -28,7 +28,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/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/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 diff --git a/config/application.rb b/config/application.rb index 8da6a7428..fabea41de 100644 --- a/config/application.rb +++ b/config/application.rb @@ -40,6 +40,7 @@ module ChouetteIhm      )      config.development_toolbar = false +    config.vehicle_journeys_extra_headers = []      unless Rails.env.production?          # Work around sprockets+teaspoon mismatch: diff --git a/config/breadcrumbs.rb b/config/breadcrumbs.rb index adcbb0b6f..e57cbc4f2 100644 --- a/config/breadcrumbs.rb +++ b/config/breadcrumbs.rb @@ -111,13 +111,23 @@ crumb :imports do |workbench|    parent :workbench, workbench  end +crumb :exports do |workbench| +  link I18n.t('exports.index.title'), workbench_exports_path(workbench) +  parent :workbench, workbench +end +  crumb :import do |workbench, import|    link breadcrumb_name(import), workbench_import_path(workbench, import)    parent :imports, workbench  end +crumb :export do |workbench, export| +  link breadcrumb_name(export), workbench_export_path(workbench, export) +  parent :exports, workbench +end +  crumb :import_resources do |import, import_resources| -  link I18n.t('import_resources.index.title'), workbench_import_import_resources_path(import.workbench, import.parent) +  link I18n.t('import.resources.index.title'), workbench_import_import_resources_path(import.workbench, import.parent)    parent :import, import.workbench, import.parent  end diff --git a/config/environments/development.rb b/config/environments/development.rb index 446e72190..7bd049979 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,3 +1,5 @@ +require Rails.root + 'config/middlewares/cachesettings' +  Rails.application.configure do    # Settings specified here will take precedence over those in config/application.rb. @@ -96,6 +98,13 @@ Rails.application.configure do    config.middleware.insert_after(ActionDispatch::Static, Rack::LiveReload) if ENV['LIVERELOAD']    config.middleware.use I18n::JS::Middleware +  config.middleware.use CacheSettings, { +    /\/assets\/.*/ => { +      cache_control: "max-age=86400, public", +      expires: 86400 +    } +  } +    config.development_toolbar = false    if ENV['TOOLBAR'] && File.exists?("config/development_toolbar.rb")      config.development_toolbar = OpenStruct.new diff --git a/config/environments/production.rb b/config/environments/production.rb index 9a699eb44..eb44e1ab1 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,3 +1,5 @@ +require Rails.root + 'config/middlewares/cachesettings' +  Rails.application.configure do    # Settings specified here will take precedence over those in config/application.rb. @@ -136,6 +138,12 @@ Rails.application.configure do    config.iev_url = ENV.fetch('IEV_URL',"http://iev:8080")    config.rails_host = ENV.fetch('RAILS_HOST','http://front:3000') +  config.middleware.use CacheSettings, { +    /\/assets\/.*/ => { +      cache_control: "max-age=#{1.year.to_i}, public", +      expires: 1.year.to_i +    } +  }    # Set node env for browserify-rails    # config.browserify_rails.node_env = "production"  end diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb index f5fb8cd5e..6b817caed 100644 --- a/config/initializers/apartment.rb +++ b/config/initializers/apartment.rb @@ -18,72 +18,76 @@ Apartment.configure do |config|    # config.excluded_models = %w{Tenant}    #    config.excluded_models = [ -    'Referential', -    'ReferentialMetadata', -    'ReferentialSuite', -    'Organisation', -    'User',      'Api::V1::ApiKey', -    'StopAreaReferential', -    'StopAreaReferentialMembership', -    'StopAreaReferentialSync', -    'StopAreaReferentialSyncMessage', -    'Chouette::StopArea', -    'LineReferential', -    'LineReferentialMembership', -    'LineReferentialSync', -    'LineReferentialSyncMessage', -    'Chouette::Line', -    'Chouette::GroupOfLine', +    'Calendar',      'Chouette::Company', +    'Chouette::GroupOfLine', +    'Chouette::Line',      'Chouette::Network', -    'ReferentialCloning', -    'Workbench', -    'Workgroup', +    'Chouette::StopArea',      'CleanUp',      'CleanUpResult', -    'Calendar', -    'Import', -    'NetexImport', -    'WorkbenchImport', -    'ImportMessage', -    'ImportResource', +    'ComplianceCheck', +    'ComplianceCheckBlock', +    'ComplianceCheckMessage', +    'ComplianceCheckResource', +    'ComplianceCheckSet',      'ComplianceControl', +    'ComplianceControlBlock', +    'ComplianceControlSet', +    'CustomField', +    'Export::Base', +    'Export::Message', +    'Export::Resource',      'GenericAttributeControl::MinMax',      'GenericAttributeControl::Pattern',      'GenericAttributeControl::Uniqueness', +    'Import::Base', +    'Import::Gtfs', +    'Import::Message', +    'Import::Netex', +    'Import::Resource', +    'Import::Workbench',      'JourneyPatternControl::Duplicates',      'JourneyPatternControl::VehicleJourney',      'LineControl::Route', +    'LineReferential', +    'LineReferentialMembership', +    'LineReferentialSync', +    'LineReferentialSyncMessage', +    'Merge', +    'Organisation', +    'Referential', +    'ReferentialCloning', +    'ReferentialMetadata', +    'ReferentialSuite',      'RouteControl::Duplicates',      'RouteControl::JourneyPattern',      'RouteControl::MinimumLength',      'RouteControl::OmnibusJourneyPattern', -    'RouteControl::OppositeRouteTerminus',      'RouteControl::OppositeRoute', +    'RouteControl::OppositeRouteTerminus',      'RouteControl::StopPointsInJourneyPattern',      'RouteControl::UnactivatedStopPoint',      'RouteControl::ZDLStopArea',      'RoutingConstraintZoneControl::MaximumLength',      'RoutingConstraintZoneControl::MinimumLength',      'RoutingConstraintZoneControl::UnactivatedStopPoint', +    'SimpleExporter', +    'SimpleImporter', +    'SimpleInterface', +    'StopAreaReferential', +    'StopAreaReferentialMembership', +    'StopAreaReferentialSync', +    'StopAreaReferentialSyncMessage', +    'User',      'VehicleJourneyControl::Delta', -    'VehicleJourneyControl::WaitingTime',      'VehicleJourneyControl::Speed',      'VehicleJourneyControl::TimeTable',      'VehicleJourneyControl::VehicleJourneyAtStops', -    'ComplianceControlSet', -    'ComplianceControlBlock', -    'ComplianceCheck', -    'ComplianceCheckSet', -    'ComplianceCheckBlock', -    'ComplianceCheckResource', -    'ComplianceCheckMessage', -    'Merge', -    'CustomField', -    'SimpleInterface', -    'SimpleImporter', -    'SimpleExporter', +    'VehicleJourneyControl::WaitingTime', +    'Workbench', +    'Workgroup',    ]    # use postgres schemas? diff --git a/config/initializers/exporters.rb b/config/initializers/exporters.rb new file mode 100644 index 000000000..dfd82a54c --- /dev/null +++ b/config/initializers/exporters.rb @@ -0,0 +1,6 @@ +SimpleExporter.define :referential_companies do |config| +  config.separator = ";" +  config.encoding = 'ISO-8859-1' +  config.add_column :name +  config.add_column :registration_number +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 2f65b8800..a177e7091 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -14,8 +14,4 @@ Sidekiq.configure_client do |config|    config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') }  end -Sidekiq.configure_client do |config| -  config.redis = { url: ENV.fetch('SIDEKIQ_REDIS_URL', 'redis://localhost:6379/12') } -end -  Sidekiq.default_worker_options = { retry: false } diff --git a/config/initializers/stif.rb b/config/initializers/stif.rb index a73e4931b..2ddadbc7e 100644 --- a/config/initializers/stif.rb +++ b/config/initializers/stif.rb @@ -27,8 +27,6 @@ Rails.application.config.to_prepare do    Organisation.before_validation(on: :create) do |organisation|      organisation.custom_view = "stif"    end -end - -Rails.application.config.to_prepare do    Dashboard.default_class = Stif::Dashboard +  Chouette::VehicleJourneyAtStop.day_offset_max = 1  end diff --git a/config/locales/compliance_controls.fr.yml b/config/locales/compliance_controls.fr.yml index 44d01a973..78b92451f 100644 --- a/config/locales/compliance_controls.fr.yml +++ b/config/locales/compliance_controls.fr.yml @@ -191,7 +191,7 @@ fr:        vehicle_journey_control/speed:          one: "La vitesse entre deux arrêts doit être dans une fourchette paramétrable"        vehicle_journey_control/delta: -        one: "Les vitesses entre 2 arrêts doivent être similaires pour toutes les courses d’une même mission" +        one: "Les temps de parcours entre 2 arrêts doivent être similaires pour toutes les courses d’une même mission"        vehicle_journey_control/time_table:          one: "Une course doit avoir au moins un calendrier d’application"        vehicle_journey_control/vehicle_journey_at_stops: diff --git a/config/locales/export_messages.en.yml b/config/locales/export_messages.en.yml new file mode 100644 index 000000000..f7951a103 --- /dev/null +++ b/config/locales/export_messages.en.yml @@ -0,0 +1,3 @@ +en: +  export_messages: +    success: Success diff --git a/config/locales/export_messages.fr.yml b/config/locales/export_messages.fr.yml new file mode 100644 index 000000000..5c2191f35 --- /dev/null +++ b/config/locales/export_messages.fr.yml @@ -0,0 +1,3 @@ +fr: +  export_messages: +    success: Succès diff --git a/config/locales/exports.en.yml b/config/locales/exports.en.yml index 2a47fba54..88c1b99f8 100644 --- a/config/locales/exports.en.yml +++ b/config/locales/exports.en.yml @@ -1,25 +1,32 @@  en: -  exports: +  exports: &exports +    search_no_results: "No export matching your query" +    filters: +      referential: "Select data space..." +      name_or_creator_cont: "Select an export or creator name..." +      error_period_filter: "End date must be greater or equal than begin date"      actions:        new: "New export" +      create: "New export" +      show: "Export report" +      download: "Download original file"        destroy: "Destroy"        destroy_confirm: "Are you sure you want destroy this export?" -    new: -      title: "New export" -      all: "All" -      flash: "Export task on queue, refresh page to see progression"      index:        title: "Exports"        warning: "" +    new: +      title: "Generate a new export" +    create: +      title: "Generate a new export"      show: +      title: "Export %{name}"        report: "Report" -      exported_file: "Exported file" -    statuses: -      started: "Started"       -      scheduled: "Processing ..." -      terminated: "Completed" -      canceled: "Canceled" -      aborted: "Failed"     +      exported_file: "Original file" +      compliance_check: "Validation report" +      compliance_check_of: "Validation of export: " +      export_of_validation: "Export of the validation" +    compliance_check_task: "Validate Report"      severities:        info: "Information"        uncheck: "Unchecked" @@ -27,7 +34,13 @@ en:        warning: "Warning"        error: "Error"        fatal: "Fatal" -  activemodel: +  export: +    workgroup: Workgroup +    netex:     Netex +    referential_companies: Companies +    base: +      <<: *exports +  activerecord:      models:        export:          zero:  "export" @@ -37,6 +50,10 @@ en:          zero:  "export"          one:   "Neptune export"          other: "exports" +      csv_export: +        zero:  "export" +        one:   "CSV export" +        other: "exports"        gtfs_export:          zero:  "export"          one:   "GTFS export" @@ -44,4 +61,39 @@ en:        netex_export:          zero:  "export"          one:   "NeTEx export" -        other: "exports"
\ No newline at end of file +        other: "exports" +    errors: +      models: +        export: +          base: +            attributes: +              file: +                wrong_file_extension: "The exported file must be a zip file" +    attributes: +      attrs: &attrs +        resources: "File to export" +        created_at: "Created on" +        started_at: "Started at" +        name: "Name" +        status: "Status" +        creator: "Creator" +        references_type: "Data to be exported" +        no_save: "No save" +        object_id_prefix: "Neptune Id prefix" +        max_distance_for_commercial: "Max distance for commercial stop" +        max_distance_for_connection_link: "Max distance for connection link" +        ignore_last_word: "ignore last word" +        ignore_end_chars: "ignore last chars" +        parent: Parent +      export: +        <<: *attrs +        base: +          <<: *attrs +        workgroup: +          duration: Duration +        referential_companies: +          referential_id: Referential +  flash: +    exports: +      create: +        notice: "The export is in progress. Please wait and refresh the page in a few moments." diff --git a/config/locales/exports.fr.yml b/config/locales/exports.fr.yml index 2d7cc0259..fa3ac8fc7 100644 --- a/config/locales/exports.fr.yml +++ b/config/locales/exports.fr.yml @@ -1,33 +1,46 @@  fr: -  exports: +  exports: &exports +    search_no_results: "Aucun export ne correspond à votre recherche" +    filters: +      referential: "Sélectionnez un jeu de données..." +      name_or_creator_cont: "Indiquez un nom d'export ou d'opérateur..." +      error_period_filter: "La date de fin doit être supérieure ou égale à la date de début"      actions:        new: "Nouvel export" +      create: "Nouvel export" +      show: "Rapport d'export" +      download: "Téléch. fichier source"        destroy: "Supprimer cet export"        destroy_confirm: "Etes vous sûr de supprimer cet export ?" -    new: -      title: "Nouvel export" -      all: "Toutes" -      flash: "La demande d'export est mise en file d'attente, veuillez rafraichir régulièrement la page pour en suivre la progression"      index:        title: "Exports"        warning: "" +    new: +      title: "Générer un export" +    create: +      title: "Générer un export"      show: +      title: "Export %{name}"        report: "Rapport" -      exported_file: "Fichier exporté" -    statuses: -      started: "En file d'attente..." -      scheduled: "En cours..." -      terminated: "Achevé" -      canceled: "Annulé" -      aborted: "Echoué" +      exported_file: "Fichier source" +      compliance_check: "Test de conformité" +      compliance_check_of: "Validation de l'export : " +      export_of_validation: "L'export de la validation" +    compliance_check_task: "Validation"      severities:        info: "Information" -      uncheck: "Non disponible" +      uncheck: "Non testé"        ok: "Ok"        warning: "Alerte"        error: "Erreur"        fatal: "Fatal" -  activemodel: +  export: +    workgroup: Groupe de travail +    netex:     Netex +    referential_companies: Transporteurs +    base: +      <<: *exports +  activerecord:      models:        export:          zero:  "export" @@ -35,7 +48,11 @@ fr:          other: "exports"        neptune_export:          zero:  "export" -        one:   "export neptune" +        one:   "export Neptune" +        other: "exports" +      csv_export: +        zero:  "export" +        one:   "export CSV"          other: "exports"        gtfs_export:          zero:  "export" @@ -45,3 +62,40 @@ fr:          zero:  "export"          one:   "export NeTEx"          other: "exports" +    errors: +      models: +        export: +          base: +            attributes: +              file: +                wrong_file_extension: "Le fichier exporté doit être au format zip" +    attributes: +      attrs: &attrs +        resources: "Fichier à exporter" +        created_at: "Créé le" +        started_at: Démarrage +        name: "Nom de l'export" +        status: "Etat" +        creator: "Opérateur" +        no_save: "Pas de sauvegarde" +        references_type: "Données à exporter" +        object_id_prefix: "Préfixe d'identifiants" +        max_distance_for_commercial: "Distance max pour créer les zones" +        max_distance_for_connection_link: "Distance max pour créer les correspondances" +        ignore_last_word: "ignorer le dernier mot" +        ignore_end_chars: "ignorer les n derniers caractères" +        type: "Type d'export" +        file: "Résultat" +        parent: Parent +      export: +        <<: *attrs +        base: +          <<: *attrs +        workgroup: +          duration: Durée +        referential_companies: +          referential_id: Jeu de données +  flash: +    exports: +      create: +        notice: "L'export est en cours, veuillez patienter. Actualiser votre page si vous voulez voir l'avancement de votre traitement." diff --git a/config/locales/footnotes.fr.yml b/config/locales/footnotes.fr.yml index 692098046..5169cfc11 100644 --- a/config/locales/footnotes.fr.yml +++ b/config/locales/footnotes.fr.yml @@ -15,6 +15,6 @@ fr:          other: "notes"      attributes:        footnote: -        code: "numéro" +        code: "titre"          checksum: Signature métier -        label: "ligne de texte" +        label: "texte" diff --git a/config/locales/import_messages.en.yml b/config/locales/import_messages.en.yml index aad4fb772..bc06c46f0 100644 --- a/config/locales/import_messages.en.yml +++ b/config/locales/import_messages.en.yml @@ -1,5 +1,5 @@  en: -  import_messages: +  import_message:      corrupt_zip_file: "The zip file %{source_filename} is corrupted and cannot be read"      inconsistent_zip_file: "The zip file %{source_filename} contains unexpected directories: %{spurious_dirs}, which are ignored"      referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" @@ -50,4 +50,4 @@ en:      2_netexstif_servicejourneypattern_2: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} doit contenir au moins 2 StopPointInJourneyPattern"      2_netexstif_servicejourneypattern_3_1: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} n'a pas de valeur pour l'attribut ServiceJourneyPatternType"      2_netexstif_servicejourneypattern_3_2: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet ServiceJourneyPattern d'identifiant %{source_objectid} a une valeur interdite %{error_value} pour l'attribut ServiceJourneyPatternType différente de 'passenger'" -    2_netexstif_servicejourneypattern_4: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number}, objet ServiceJourneyPattern d'identifiant %{source_objectid} : les attributs 'order' des StopPointInJourneyPattern ne sont pas croissants."
\ No newline at end of file +    2_netexstif_servicejourneypattern_4: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number}, objet ServiceJourneyPattern d'identifiant %{source_objectid} : les attributs 'order' des StopPointInJourneyPattern ne sont pas croissants." diff --git a/config/locales/import_resources.en.yml b/config/locales/import_resources.en.yml index 5f0f3213e..386039319 100644 --- a/config/locales/import_resources.en.yml +++ b/config/locales/import_resources.en.yml @@ -1,11 +1,14 @@  en: +  import: +    resources: &resources +      index: +        title: "NeTEx conformity" +        table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile" +        table_title: "Satus of anlyzed files" +        table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed." +        metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"    import_resources: -    index: -      title: "NeTEx conformity" -      table_state: "%{lines_imported} line(s) imported on %{lines_in_zipfile} presents in zipfile" -      table_title: "Satus of anlyzed files" -      table_explanation: "When calendriers.xml and/or commun.xml are not imported, then all lines file are not processed." -      metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +    <<: *resources    activerecord:      models:        import_resource: @@ -14,5 +17,6 @@ en:          other: "netex conformities"      attributes:        import: -        name: "Filename" -        status: "Status" +        resource: +          name: "Filename" +          status: "Status" diff --git a/config/locales/import_resources.fr.yml b/config/locales/import_resources.fr.yml index a271ae1ca..50fb7f1ca 100644 --- a/config/locales/import_resources.fr.yml +++ b/config/locales/import_resources.fr.yml @@ -1,11 +1,14 @@  fr: +  import: +    resources: &resources +      index: +        title: "Rapport de conformité NeTEx" +        table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive" +        table_title: "Etat des fichiers analysés" +        table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité." +        metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a"    import_resources: -    index: -      title: "Rapport de conformité NeTEx" -      table_state: "%{lines_imported} ligne(s) importée(s) sur %{lines_in_zipfile} présente(s) dans l'archive" -      table_title: "Etat des fichiers analysés" -      table_explanation: "Dans le cas ou le(s) fichiers calendriers.xml et/ou commun.xml sont dans un état non importé, alors tous les fichiers lignes sont automatiquement dans un état non traité." -      metrics: "%{ok_count} ok, %{error_count} errors, %{warning_count} warnings, %{uncheck_count} n/a" +    <<: *resources    activerecord:      models:        import_resource: @@ -13,6 +16,7 @@ fr:          one:   "rapport de conformité Netex"          other: "rapports de conformité Netex"      attributes: -      import_resource: -        name: "Fichier" -        status: "Etat" +      import: +        resource: +          name: "Fichier" +          status: "Etat" diff --git a/config/locales/import_tasks.en.yml b/config/locales/import_tasks.en.yml deleted file mode 100644 index 34f7e6998..000000000 --- a/config/locales/import_tasks.en.yml +++ /dev/null @@ -1,52 +0,0 @@ -en: -  import_tasks: -    actions: -      new: "New import" -    new: -      title: "New import" -      all: "All" -      flash: "Import task on queue, refresh page to see progression" -  activemodel: -    models: -      import_task: -        zero:  "import" -        one:   "import" -        other: "imports" -      neptune_import: -        zero:  "import" -        one:   "Neptune import" -        other: "imports" -      gtfs_import: -        zero:  "import" -        one:   "GTFS import" -        other: "imports" -      netex_import: -        zero:  "import" -        one:   "NeTEx import" -        other: "imports" -    attributes: -      import_task: -        name: "Import name" -        no_save: "No save" -        resources: "File to import" -        references_type: "Data to be imported" -        object_id_prefix: "Neptune Id prefix" -        max_distance_for_commercial: "Max distance for commercial stop" -        max_distance_for_connection_link: "Max distance for connection link" -        ignore_last_word: "ignore last word" -        ignore_end_chars: "ignore last chars" -    errors: -      models: -        import_task: -          attributes: -            resources: -              maximum_file_size: "File should smaller than %{maximum_file_size} (%{file_size} sent)" -              invalid_mime_type: "File doesn't use the expected format (%{mime_type} sent)" -  formtastic: -    titles: -      import_task: -        max_distance_for_commercial: "Maximal distance to merge homonymous stops in commercial stop in meter" -        max_distance_for_connection_link: "Maximal distance to link stops by connection link stop in meter" -        ignore_last_word: "ignore last word on stop name in homonymous detection (inappliable when just one word occurs)" -        ignore_end_chars: "ignore some chars at the end of stop names in homonymous detection" -        references_type: "Filter on stop areas import only GTFS stops and transfers files, these may contain extra attributes" diff --git a/config/locales/import_tasks.fr.yml b/config/locales/import_tasks.fr.yml deleted file mode 100644 index 002ca03cb..000000000 --- a/config/locales/import_tasks.fr.yml +++ /dev/null @@ -1,53 +0,0 @@ -fr: -  import_tasks: -    actions: -      new: "Nouvel import" -    new: -      title: "Nouvel import" -      all: "Tout" -      flash: "La demande d'import est mise en file d'attente, veuillez rafraichir régulièrement la page pour en suivre la progression" -  activemodel: -    models: -      import_task: -        zero:  "import" -        one:   "import" -        other: "imports" -      neptune_import: -        zero:  "import" -        one:   "import Neptune" -        other: "imports" -      gtfs_import: -        zero:  "import" -        one:   "import GTFS" -        other: "imports" -      netex_import: -        zero:  "import" -        one:   "import NeTEx" -        other: "imports" -    attributes: -      import_task: -        name: "Nom de l'import" -        no_save: "Pas de sauvegarde" -        resources: "Fichier à importer" -        references_type: "Données à importer" -        object_id_prefix: "Préfixe d'identifiants" -        max_distance_for_commercial: "Distance max pour créer les zones" -        max_distance_for_connection_link: "Distance max pour créer les correspondances" -        ignore_last_word: "ignorer le dernier mot" -        ignore_end_chars: "ignorer les n derniers caractères" -    errors: -      models: -        import_task: -          attributes: -            resources: -              maximum_file_size: "Le fichier ne peut dépasser %{maximum_file_size} (%{file_size} soumis)" -              invalid_mime_type: "Le fichier n'utilise pas le format attendu (%{mime_type} soumis)" - -  formtastic: -    titles: -      import_task: -        max_distance_for_commercial: "Distance maximale entre deux arrêts homonymes pour créer les zones d'arrêt (en mètre)" -        max_distance_for_connection_link: "Distance maximale entre deux arrêts pour créer les correspondances (en mètre)" -        ignore_last_word: "Ignorer le dernier mot pour détecter l'homonymie des noms d'arrêt (inapplicable quand le nom ne comporte qu'un mot)" -        ignore_end_chars: "Ignorer les n derniers caractères du nom de l'arrêt pour détecter l'homonymie" -        references_type: "Le filtre sur arrêts importe uniquement les fichiers GTFS stops et transfers gtfs, ceux-ci pouvant contenir des attributs supplémentaires" diff --git a/config/locales/imports.en.yml b/config/locales/imports.en.yml index b0644acd3..d0db87fb1 100644 --- a/config/locales/imports.en.yml +++ b/config/locales/imports.en.yml @@ -59,11 +59,12 @@ en:      errors:        models:          import: -          attributes: -            file: -              wrong_file_extension: "The imported file must be a zip file" +          base: +            attributes: +              file: +                wrong_file_extension: "The imported file must be a zip file"      attributes: -      import: +      attrs: &attrs          resources: "File to import"          created_at: "Created on"          started_at: "Started at" @@ -77,6 +78,10 @@ en:          max_distance_for_connection_link: "Max distance for connection link"          ignore_last_word: "ignore last word"          ignore_end_chars: "ignore last chars" +      import: +        <<: *attrs +        base: +          <<: *attrs    flash:      imports:        create: diff --git a/config/locales/imports.fr.yml b/config/locales/imports.fr.yml index 2380eac45..40272889a 100644 --- a/config/locales/imports.fr.yml +++ b/config/locales/imports.fr.yml @@ -1,5 +1,5 @@  fr: -  imports: +  imports: &imports      search_no_results: "Aucun import ne correspond à votre recherche"      filters:        referential: "Sélectionnez un jeu de données..." @@ -34,6 +34,9 @@ fr:        warning: "Alerte"        error: "Erreur"        fatal: "Fatal" +  import: +    base: +      <<: *imports    activerecord:      models:        import: @@ -59,11 +62,12 @@ fr:      errors:        models:          import: -          attributes: -            file: -              wrong_file_extension: "Le fichier importé doit être au format zip" +          base: +            attributes: +              file: +                wrong_file_extension: "Le fichier importé doit être au format zip"      attributes: -      import: +      attrs: &attrs          resources: "Fichier à importer"          created_at: "Créé le"          started_at: Démarrage @@ -77,6 +81,12 @@ fr:          max_distance_for_connection_link: "Distance max pour créer les correspondances"          ignore_last_word: "ignorer le dernier mot"          ignore_end_chars: "ignorer les n derniers caractères" + +      import: +        <<: *attrs +        base: +          <<: *attrs +    flash:      imports:        create: diff --git a/config/locales/journey_patterns.fr.yml b/config/locales/journey_patterns.fr.yml index ad5869e84..2aa95248f 100644 --- a/config/locales/journey_patterns.fr.yml +++ b/config/locales/journey_patterns.fr.yml @@ -42,7 +42,7 @@ fr:          published_name: "Nom public"          section_status: "Status section"          comment: "Commentaire" -        registration_number: "Numéro d'enregistrement" +        registration_number: "Code mission"          stop_point_ids: "Sélection des arrêts desservis"          objectid: "Identifiant Neptune"          object_version: "Version" diff --git a/config/locales/routing_constraint_zones.fr.yml b/config/locales/routing_constraint_zones.fr.yml index 024dd3288..d4b97fff2 100644 --- a/config/locales/routing_constraint_zones.fr.yml +++ b/config/locales/routing_constraint_zones.fr.yml @@ -2,9 +2,9 @@ fr:    activerecord:      models:        routing_constraint_zone: -        zero: zone de contrainte -        one: zone de contrainte -        other: zone de contraintes +        zero: ITL +        one: ITL +        other: ITLs      attributes:        routing_constraint_zone:          checksum: Signature métier diff --git a/config/locales/stop_areas.en.yml b/config/locales/stop_areas.en.yml index ac3dce280..389f70c0c 100644 --- a/config/locales/stop_areas.en.yml +++ b/config/locales/stop_areas.en.yml @@ -5,6 +5,10 @@ en:      errors:        empty: Aucun stop_area_id        parent_area_type: can not be of type %{area_type} +      registration_number: +        already_taken: Already taken +        cannot_be_empty: This field is mandatory +        invalid: Incorrect value      default_geometry_success: "%{count} modified stop areas"      stop_area:        no_position: "No Position" @@ -106,8 +110,11 @@ en:          name: "Name"          registration_number: "Registration number"          published_name: "Published name" -        deleted: "Deleted" -        deleted_at: "Deleted at" +        in_creation: "In creation" +        confirmed: "Activated" +        confirmed_at: "Activated at" +        deleted: "Deactivated" +        deleted_at: "Deactivated at"          comment: "Description"          stop_area_type: "Area type"          area_type: "Area type" @@ -139,8 +146,6 @@ en:          coordinates: "Coordinates (lat,lng) WGS84"          zip_code: "Zip code"          city_name: "City" -        created_at: Created at -        updated_at: Updated at          waiting_time: Waiting time (minutes)          state: State    formtastic: @@ -148,6 +153,7 @@ en:        stop_area:          name: ""          registration_number: "only alphanumerical or underscore characters" +        registration_number_format: "authorized format : %{registration_number_format}"          objectid: "[prefix]:StopArea:[unique_key] : prefix contains only alphanumerical or underscore characters, unique_key accepts also minus character"          nearest_topic_name: ""          city_name: "" diff --git a/config/locales/stop_areas.fr.yml b/config/locales/stop_areas.fr.yml index f75c4ebe7..aee112be7 100644 --- a/config/locales/stop_areas.fr.yml +++ b/config/locales/stop_areas.fr.yml @@ -6,6 +6,10 @@ fr:        empty: Aucun stop_area_id        parent_area_type: ne peut être de type %{area_type}        incorrect_kind_area_type: Ce type d'arrêt est invalide pour cette catégorie +      registration_number: +        already_taken: Déjà utilisé +        cannot_be_empty: Ce champ est requis +        invalid: Valeur invalide      default_geometry_success: "%{count} arrêts édités"      stop_area:        no_position: "Pas de position" @@ -108,8 +112,12 @@ fr:          kind: "Catégorie"          registration_number: "Numéro d'enregistrement"          published_name: "Nom public" -        deleted: "Supprimé" -        deleted_at: "Activé" +        in_creation: "En création" +        confirmed: "Actif" +        confirmed_at: "Activé le" +        deleted: "Désactivé" +        deactivated: "Désactivé" +        deleted_at: "Désactivé le"          comment: "Commentaire"          stop_area_type: "Type d'arrêt"          area_type: "Type d'arrêt" @@ -141,8 +149,6 @@ fr:          coordinates: "Coordonnées (lat,lng) WGS84"          zip_code: "Code postal"          city_name: "Commune" -        created_at: "Créé le" -        updated_at: "Edité le"          waiting_time: Temps de desserte (minutes)          state: État    formtastic: @@ -150,6 +156,7 @@ fr:        stop_area:          name: ""          registration_number: "caractères autorisés : alphanumériques et 'souligné'" +        registration_number_format: "format autorisé: %{registration_number_format}"          objectid: "[prefixe]:StopArea:[clé_unique]  caractères autorisés : alphanumériques et 'souligné' pour le préfixe, la clé unique accepte en plus le 'moins'"          nearest_topic_name: ""          city_name: "" diff --git a/config/locales/vehicle_journeys.fr.yml b/config/locales/vehicle_journeys.fr.yml index a43fa4580..a1eb5b3f7 100644 --- a/config/locales/vehicle_journeys.fr.yml +++ b/config/locales/vehicle_journeys.fr.yml @@ -122,7 +122,7 @@ fr:          object_version: "Version"          objectid: "Identifiant Neptune"          on_demand_fs: "Service à la demande" -        published_journey_identifier: "Identifiant public" +        published_journey_identifier: "Numéro de train"          published_journey_name: "Nom public"          purchase_window: "Disponibilité commerciale"          regular_fs: "Service régulier" diff --git a/config/middlewares/cachesettings.rb b/config/middlewares/cachesettings.rb new file mode 100644 index 000000000..8a122891f --- /dev/null +++ b/config/middlewares/cachesettings.rb @@ -0,0 +1,20 @@ +class CacheSettings +  def initialize app, pat +    @app = app +    @pat = pat +  end + +  def call env +    res = @app.call(env) +    path = env["REQUEST_PATH"] +    @pat.each do |pattern,data| +      if path =~ pattern +        res[1]["Cache-Control"] = data[:cache_control] if data.has_key?(:cache_control) +        res[1]["Expires"] = (Time.now + data[:expires]).utc.rfc2822 if data.has_key?(:expires) +        res[1]["X-Custom-Cache-Control"] = "yes" if Rails.env.development? +        return res +      end +    end +    res +  end +end diff --git a/config/routes.rb b/config/routes.rb index b6934936b..6313b5678 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,12 @@ ChouetteIhm::Application.routes.draw do          resources :import_messages, only: [:index]        end      end +    resources :exports do +      post :upload, on: :member +      resources :export_resources, only: [:index] do +        resources :export_messages, only: [:index] +      end +    end      resources :compliance_check_sets, only: [:index, :show] do        get :executed, on: :member        resources :compliance_checks, only: [:show] diff --git a/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb b/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb index c14450387..a08a10b9a 100644 --- a/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb +++ b/db/migrate/20171106111448_update_import_message_criticity_type_to_string.rb @@ -7,10 +7,10 @@ class UpdateImportMessageCriticityTypeToString < ActiveRecord::Migration        when 0 then "info"        when 1 then "warning"        when 2 then "error" -      else  +      else          "info"        end      end -    ImportMessage.all.each { |im| im.update_attribute(:criticity, change_criticity_value(im.criticity)) } +    Import::Message.all.each { |im| im.update_attribute(:criticity, change_criticity_value(im.criticity)) }    end  end diff --git a/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb b/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb new file mode 100644 index 000000000..3f4231acb --- /dev/null +++ b/db/migrate/20180226074739_add_registration_number_format_to_stop_area_referentials.rb @@ -0,0 +1,5 @@ +class AddRegistrationNumberFormatToStopAreaReferentials < ActiveRecord::Migration +  def change +    add_column :stop_area_referentials, :registration_number_format, :string +  end +end diff --git a/db/migrate/20180306135204_clean_former_exports.rb b/db/migrate/20180306135204_clean_former_exports.rb new file mode 100644 index 000000000..46a595c12 --- /dev/null +++ b/db/migrate/20180306135204_clean_former_exports.rb @@ -0,0 +1,5 @@ +class CleanFormerExports < ActiveRecord::Migration +  def change +    drop_table :exports +  end +end diff --git a/db/migrate/20180306152953_update_imports_names.rb b/db/migrate/20180306152953_update_imports_names.rb new file mode 100644 index 000000000..fc57ff849 --- /dev/null +++ b/db/migrate/20180306152953_update_imports_names.rb @@ -0,0 +1,13 @@ +class UpdateImportsNames < ActiveRecord::Migration +  def change +    Import::Base.all.pluck(:type).uniq.each do |type| +      next if type =~ /^Import/ +      Import::Base.where(type: type).update_all type: "Import::#{type.gsub 'Import', ''}" +    end + +    Import::Base.all.pluck(:parent_type).uniq.compact.each do |type| +      next if type =~ /^Import/ +      Import::Base.where(parent_type: type).update_all parent_type: "Import::#{type.gsub 'Import', ''}" +    end +  end +end diff --git a/db/migrate/20180307071448_create_new_exports.rb b/db/migrate/20180307071448_create_new_exports.rb new file mode 100644 index 000000000..74921d108 --- /dev/null +++ b/db/migrate/20180307071448_create_new_exports.rb @@ -0,0 +1,56 @@ +class CreateNewExports < ActiveRecord::Migration +  def change +    create_table :exports do |t| +      t.string   "status" +      t.string   "current_step_id" +      t.float    "current_step_progress" +      t.integer  "workbench_id",          limit: 8 +      t.integer  "referential_id",        limit: 8 +      t.string   "name" +      t.datetime "created_at" +      t.datetime "updated_at" +      t.string   "file" +      t.datetime "started_at" +      t.datetime "ended_at" +      t.string   "token_upload" +      t.string   "type" +      t.integer  "parent_id",             limit: 8 +      t.string   "parent_type" +      t.datetime "notified_parent_at" +      t.integer  "current_step",          default: 0 +      t.integer  "total_steps",           default: 0 +      t.string   "creator" +    end + +    add_index "exports", ["referential_id"], name: "index_exports_on_referential_id", using: :btree +    add_index "exports", ["workbench_id"], name: "index_exports_on_workbench_id", using: :btree + +    create_table "export_messages", id: :bigserial, force: :cascade do |t| +      t.string   "criticity" +      t.string   "message_key" +      t.hstore   "message_attributes" +      t.integer  "export_id",           limit: 8 +      t.integer  "resource_id",         limit: 8 +      t.datetime "created_at" +      t.datetime "updated_at" +      t.hstore   "resource_attributes" +    end + +    add_index "export_messages", ["export_id"], name: "index_export_messages_on_export_id", using: :btree +    add_index "export_messages", ["resource_id"], name: "index_export_messages_on_resource_id", using: :btree + +    create_table "export_resources", id: :bigserial, force: :cascade do |t| +      t.integer  "export_id",     limit: 8 +      t.string   "status" +      t.datetime "created_at" +      t.datetime "updated_at" +      t.string   "resource_type" +      t.string   "reference" +      t.string   "name" +      t.hstore   "metrics" +    end + +    add_index "export_resources", ["export_id"], name: "index_export_resources_on_export_id", using: :btree + +  end +end diff --git a/db/migrate/20180307202627_add_confirmed_at_to_stop_areas.rb b/db/migrate/20180307202627_add_confirmed_at_to_stop_areas.rb new file mode 100644 index 000000000..602d3afdc --- /dev/null +++ b/db/migrate/20180307202627_add_confirmed_at_to_stop_areas.rb @@ -0,0 +1,5 @@ +class AddConfirmedAtToStopAreas < ActiveRecord::Migration +  def change +    add_column :stop_areas, :confirmed_at, :datetime +  end +end diff --git a/db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb b/db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb new file mode 100644 index 000000000..7bc0471f7 --- /dev/null +++ b/db/migrate/20180308063549_update_stop_areas_confirmed_at_attribute.rb @@ -0,0 +1,9 @@ +class UpdateStopAreasConfirmedAtAttribute < ActiveRecord::Migration +   def up +    Chouette::StopArea.where(deleted_at: nil).update_all(confirmed_at: Time.now) +  end + +  def down +    raise ActiveRecord::IrreversibleMigration +  end +end diff --git a/db/migrate/20180308095116_add_options_to_exports.rb b/db/migrate/20180308095116_add_options_to_exports.rb new file mode 100644 index 000000000..02744c5cb --- /dev/null +++ b/db/migrate/20180308095116_add_options_to_exports.rb @@ -0,0 +1,5 @@ +class AddOptionsToExports < ActiveRecord::Migration +  def change +    add_column :exports, :options, :hstore +  end +end diff --git a/db/schema.rb b/db/schema.rb index e29d076e0..2f9ffa840 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,8 +11,7 @@  #  # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180301142531) do - +ActiveRecord::Schema.define(version: 20180308095116) do    # These are extensions that must be enabled in order to support this database    enable_extension "plpgsql"    enable_extension "postgis" @@ -421,9 +420,9 @@ ActiveRecord::Schema.define(version: 20180301142531) do      t.string   "type"      t.integer  "parent_id",             limit: 8      t.string   "parent_type" -    t.datetime "notified_parent_at"      t.integer  "current_step",                    default: 0      t.integer  "total_steps",                     default: 0 +    t.datetime "notified_parent_at"      t.string   "creator"    end @@ -801,6 +800,7 @@ ActiveRecord::Schema.define(version: 20180301142531) do      t.integer  "waiting_time"      t.string   "kind"      t.jsonb    "localized_names" +    t.datetime "confirmed_at"    end    add_index "stop_areas", ["name"], name: "index_stop_areas_on_name", using: :btree diff --git a/lib/model_attribute.rb b/lib/model_attribute.rb index 872c00152..4880d2490 100644 --- a/lib/model_attribute.rb +++ b/lib/model_attribute.rb @@ -64,27 +64,16 @@ class ModelAttribute    # Chouette::Route    define :route, :name, :string    define :route, :published_name, :string -  define :route, :comment, :string -  define :route, :number, :string -  define :route, :direction, :string    define :route, :wayback, :string    # Chouette::JourneyPattern    define :journey_pattern, :name, :string    define :journey_pattern, :published_name, :string -  define :journey_pattern, :comment, :string    define :journey_pattern, :registration_number, :string -  define :journey_pattern, :section_status, :integer    # Chouette::VehicleJourney -  define :vehicle_journey, :comment, :string -  define :vehicle_journey, :status_value, :string -  define :vehicle_journey, :transport_mode, :string -  define :vehicle_journey, :facility, :string    define :vehicle_journey, :published_journey_name, :string    define :vehicle_journey, :published_journey_identifier, :string -  define :vehicle_journey, :vehicle_type_identifier, :string -  define :vehicle_journey, :number, :integer    define :vehicle_journey, :mobility_restricted_suitability, :boolean    define :vehicle_journey, :flexible_service, :boolean @@ -92,13 +81,6 @@ class ModelAttribute    define :footnote, :code, :string    define :footnote, :label, :string -  # Chouette::TimeTable -  define :time_table, :version, :string -  define :time_table, :comment, :string -  define :time_table, :start_date, :date -  define :time_table, :end_date, :date -  define :time_table, :color, :string -    # Chouette::RoutingConstraintZone    define :routing_constraint_zone, :name, :string diff --git a/lib/stif/permission_translator.rb b/lib/stif/permission_translator.rb index 9e0feb9b8..09a7c610c 100644 --- a/lib/stif/permission_translator.rb +++ b/lib/stif/permission_translator.rb @@ -21,6 +21,7 @@ module Stif          calendars          footnotes          imports +        exports          merges          journey_patterns          referentials diff --git a/lib/tasks/exports.rake b/lib/tasks/exports.rake index 547388b35..845d581d3 100644 --- a/lib/tasks/exports.rake +++ b/lib/tasks/exports.rake @@ -51,6 +51,9 @@ namespace :export do      if journeys.count == 0        puts "No maching journeys were found".red      else +      exports_group = SimpleInterfacesGroup.new "Export Complet \"#{referential.name}\" du #{Time.now.to_date} au #{args[:timelapse].to_i.days.from_now.to_date}" +      exports_group.shared_options = {verbose: true} +        exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_companies", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_companies.json"        ids = journeys.pluck :company_id        ids += journeys.joins(route: :line).pluck :"lines.company_id" @@ -59,39 +62,37 @@ namespace :export do          config.collection = Chouette::Company.where(id: ids.uniq).order('name')        end -      SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args -      break if exporter.status == :error +      exports_group.add_interface exporter, "Services Types", :export        exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_schedules", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_schedules.json"        exporter.configure do |config|          config.collection = journeys        end -      SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args -      break if exporter.status == :error +      exports_group.add_interface exporter, "Schedules", :export        exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_routes", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_routes.json"        exporter.configure do |config|          config.collection = Chouette::JourneyPattern.where(id: journeys.pluck(:journey_pattern_id).uniq)        end -      SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args -      break if exporter.status == :error +      exports_group.add_interface exporter, "Routes", :export        exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_stops", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_stops.json"        exporter.configure do |config|          config.collection = Chouette::StopArea.where(id: journeys.joins(:stop_points).pluck(:"stop_points.stop_area_id").uniq).order('parent_id ASC NULLS FIRST')        end -      SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args -      break if exporter.status == :error +      exports_group.add_interface exporter, "Stops", :export        exporter = SimpleJsonExporter.create configuration_name: "#{args[:configuration_name]}_journeys", filepath: "#{args[:output_dir]}/#{args[:configuration_name]}_journeys.json"        exporter.configure do |config|          config.collection = journeys        end -      SimpleInterfacesHelper.run_interface_controlling_interruption exporter, :export, args +      exports_group.add_interface exporter, "Services", :export + +      exports_group.run      end    end  end diff --git a/lib/tasks/helpers/simple_interfaces.rb b/lib/tasks/helpers/simple_interfaces.rb index 5b593be43..61dd38399 100644 --- a/lib/tasks/helpers/simple_interfaces.rb +++ b/lib/tasks/helpers/simple_interfaces.rb @@ -1,28 +1,11 @@  module SimpleInterfacesHelper -  def self.interface_output_to_csv interface, output_dir -    FileUtils.mkdir_p output_dir -    filepath =  File.join output_dir, + "#{interface.configuration_name}_#{Time.now.strftime "%y%m%d%H%M"}_out.csv" -    cols = %w(line kind event message error) -    if interface.reload.journal.size > 0 && interface.journal.first["row"].present? -      keys = interface.journal.first["row"].map(&:first) -      CSV.open(filepath, "w") do |csv| -        csv << cols + keys -        interface.journal.each do |j| -          csv << cols.map{|c| j[c]} + j["row"].map(&:last) -        end -      end -      puts "Task Output written in #{filepath}" -    end -  end -    def self.run_interface_controlling_interruption interface, method, args      begin        interface.send(method, verbose: true)      rescue Interrupt +      interface.write_output_to_csv        raise      ensure -      puts "\n\e[33m***\e[0m Done, status: " + (interface.status == "success" ? "\e[32m" : "\e[31m" ) + (interface.status || "") + "\e[0m" -      interface_output_to_csv interface, args[:logs_output_dir]      end    end  end diff --git a/spec/controllers/api/v1/imports_controller_spec.rb b/spec/controllers/api/v1/imports_controller_spec.rb index 8077dd052..f7022115a 100644 --- a/spec/controllers/api/v1/imports_controller_spec.rb +++ b/spec/controllers/api/v1/imports_controller_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Api::V1::ImportsController, type: :controller do        it 'should be successful' do          expect {            post :create, workbench_id: workbench.id, workbench_import: {name: "test", file: file, creator: 'test'}, format: :json -        }.to change{WorkbenchImport.count}.by(1) +        }.to change{Import::Workbench.count}.by(1)          expect(response).to be_success        end      end diff --git a/spec/controllers/exports_controller_spec.rb b/spec/controllers/exports_controller_spec.rb index 6cd6e4c54..3a67497ec 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -1,22 +1,97 @@ -require 'spec_helper' - -describe ExportsController, :type => :controller do +RSpec.describe ExportsController, :type => :controller do    login_user -  describe "GET 'new'" do -    it "returns http success" do -      pending -      get 'new' +  let(:workbench) { create :workbench } +  let(:export)    { create(:netex_export, workbench: workbench) } + +  describe 'GET #new' do +    it 'should be successful if authorized' do +      get :new, workbench_id: workbench.id        expect(response).to be_success      end + +    it 'should be unsuccessful unless authorized' do +      remove_permissions('exports.create', from_user: @user, save: true) +      get :new, workbench_id: workbench.id +      expect(response).not_to be_success +    end    end -  describe "GET 'index'" do -    it "returns http success" do -      pending -      get 'index' -      expect(response).to be_success +  describe "POST #create" do +    let(:params){ {name: "foo"} } +    let(:request){ post :create, workbench_id: workbench.id, export: params  } +    it 'should create no objects' do +      expect{request}.to_not change{Export::Base.count} +    end + +    context "with full params" do +      let(:params){{ +        name: "foo", +        type: "Export::Netex", +        duration: 12, +        export_type: :line +      }} + +      it 'should be successful' do +        expect{request}.to change{Export::Base.count}.by(1) +      end + +      it "displays a flash message" do +        request +        expect(controller).to set_flash[:notice].to( +          I18n.t('flash.exports.create.notice') +        ) +      end +    end + +    context "with missing options" do +      let(:params){{ +        name: "foo", +        type: "Export::Workgroup" +      }} + +      it 'should be unsuccessful' do +        expect{request}.to change{Export::Base.count}.by(0) +      end +    end + +    context "with all options" do +      let(:params){{ +        name: "foo", +        type: "Export::Workgroup", +        duration: 90 +      }} + +      it 'should be successful' do +        expect{request}.to change{Export::Base.count}.by(1) +      end +    end + +    context "with wrong type" do +      let(:params){{ +        name: "foo", +        type: "Export::Foo" +      }} + +      it 'should be unsuccessful' do +        expect{request}.to raise_error ActiveRecord::SubclassNotFound +      end      end    end +  describe 'POST #upload' do +    context "with the token" do +      it 'should be successful' do +        post :upload, workbench_id: workbench.id, id: export.id, token: export.token_upload +        expect(response).to be_redirect +      end +    end + +    context "without the token" do +      it 'should be unsuccessful' do +        post :upload, workbench_id: workbench.id, id: export.id, token: "foo" +        expect(response).to_not be_success +      end +    end +  end  end diff --git a/spec/controllers/referential_vehicle_journeys_controller_spec.rb b/spec/controllers/referential_vehicle_journeys_controller_spec.rb index 50230dd9e..6bf4c7553 100644 --- a/spec/controllers/referential_vehicle_journeys_controller_spec.rb +++ b/spec/controllers/referential_vehicle_journeys_controller_spec.rb @@ -80,6 +80,18 @@ RSpec.describe ReferentialVehicleJourneysController, type: :controller do            expect(assigns[:vehicle_journeys]).to_not include(vehicle_journey_2)          end        end + +      context "with both stops one being unused" do +        let(:from_area_id){ vehicle_journey_1.stop_areas.first.id } +        let(:to_area_id){ +          stop_area = create :stop_area +          create :stop_point, stop_area: stop_area, route: vehicle_journey_1.route +          stop_area +        } +        it "should apply filters" do +          expect(assigns[:vehicle_journeys]).to eq [] +        end +      end      end    end diff --git a/spec/controllers/vehicle_journey_imports_controller_spec.rb b/spec/controllers/vehicle_journey_imports_controller_spec.rb deleted file mode 100644 index 633f90b70..000000000 --- a/spec/controllers/vehicle_journey_imports_controller_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe ImportTasksController, :type => :controller do -  login_user -end diff --git a/spec/factories/exports.rb b/spec/factories/exports.rb deleted file mode 100644 index 34427edb8..000000000 --- a/spec/factories/exports.rb +++ /dev/null @@ -1,5 +0,0 @@ -FactoryGirl.define do -  factory :export do -    referential { Referential.find_by_slug("first") } -  end -end diff --git a/spec/factories/exports/export_messages.rb b/spec/factories/exports/export_messages.rb new file mode 100644 index 000000000..55394ec45 --- /dev/null +++ b/spec/factories/exports/export_messages.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do +  factory :export_message, class: Export::Message do +    association :export +    association :resource, factory: :export_resource +    criticity :info  +  end +end diff --git a/spec/factories/exports/export_resources.rb b/spec/factories/exports/export_resources.rb new file mode 100644 index 000000000..8e38235cd --- /dev/null +++ b/spec/factories/exports/export_resources.rb @@ -0,0 +1,9 @@ +FactoryGirl.define do +  factory :export_resource, class: Export::Resource do +    sequence(:name) { |n| "Export resource #{n}" } +    association :export, factory: :netex_export +    status :WARNING +    resource_type 'type' +    reference 'reference' +  end +end diff --git a/spec/factories/exports/exports.rb b/spec/factories/exports/exports.rb new file mode 100644 index 000000000..c8aaf30a9 --- /dev/null +++ b/spec/factories/exports/exports.rb @@ -0,0 +1,34 @@ +FactoryGirl.define do +  factory :export, class: Export::Base do +    sequence(:name) { |n| "Export #{n}" } +    current_step_id "MyString" +    current_step_progress 1.5 +    association :workbench +    association :referential +    status :new +    started_at nil +    ended_at nil +    creator 'rspec' + +    after(:build) do |export| +      export.class.skip_callback(:create, :before, :initialize_fields) +    end +  end + +  factory :bad_export, class: Export::Base do +    sequence(:name) { |n| "Export #{n}" } +    current_step_id "MyString" +    current_step_progress 1.5 +    association :workbench +    association :referential +    file {File.open(File.join(Rails.root, 'spec', 'fixtures', 'terminated_job.json'))} +    status :new +    started_at nil +    ended_at nil +    creator 'rspec' + +    after(:build) do |export| +      export.class.skip_callback(:create, :before, :initialize_fields) +    end +  end +end diff --git a/spec/factories/exports/netex_exports.rb b/spec/factories/exports/netex_exports.rb new file mode 100644 index 000000000..0648bbc56 --- /dev/null +++ b/spec/factories/exports/netex_exports.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do +  factory :netex_export, class: Export::Netex, parent: :export do +    association :parent, factory: :workgroup_export +    export_type :line +    duration 90 +  end +end diff --git a/spec/factories/exports/workgroup_exports.rb b/spec/factories/exports/workgroup_exports.rb new file mode 100644 index 000000000..f5dfb6b94 --- /dev/null +++ b/spec/factories/exports/workgroup_exports.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do +  factory :workgroup_export, class: Export::Workgroup, parent: :export do +    duration 90 +  end +end diff --git a/spec/factories/import_messages.rb b/spec/factories/imports/import_messages.rb index 5d936679a..f5edf1685 100644 --- a/spec/factories/import_messages.rb +++ b/spec/factories/imports/import_messages.rb @@ -1,5 +1,5 @@  FactoryGirl.define do -  factory :import_message do +  factory :import_message, class: Import::Message do      association :import      association :resource, factory: :import_resource      criticity :info diff --git a/spec/factories/import_resources.rb b/spec/factories/imports/import_resources.rb index 76afcc486..aaf7e3111 100644 --- a/spec/factories/import_resources.rb +++ b/spec/factories/imports/import_resources.rb @@ -1,5 +1,5 @@  FactoryGirl.define do -  factory :import_resource do +  factory :import_resource, class: Import::Resource do      association :import      status :WARNING      sequence(:name) { |n| "Import resource #{n}" } diff --git a/spec/factories/imports.rb b/spec/factories/imports/imports.rb index e07447b60..cb7764cc6 100644 --- a/spec/factories/imports.rb +++ b/spec/factories/imports/imports.rb @@ -1,5 +1,5 @@  FactoryGirl.define do -  factory :import do +  factory :import, class: Import::Base do      sequence(:name) { |n| "Import #{n}" }      current_step_id "MyString"      current_step_progress 1.5 @@ -16,7 +16,7 @@ FactoryGirl.define do      end    end -  factory :bad_import do +  factory :bad_import, class: Import::Base do      sequence(:name) { |n| "Import #{n}" }      current_step_id "MyString"      current_step_progress 1.5 diff --git a/spec/factories/netex_imports.rb b/spec/factories/imports/netex_imports.rb index b59267a0a..7ee6839e8 100644 --- a/spec/factories/netex_imports.rb +++ b/spec/factories/imports/netex_imports.rb @@ -1,7 +1,7 @@  FactoryGirl.define do -  factory :netex_import, class: NetexImport, parent: :import do +  factory :netex_import, class: Import::Netex, parent: :import do      file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) }      association :parent, factory: :workbench_import -     +    end  end diff --git a/spec/factories/workbench_imports.rb b/spec/factories/imports/workbench_imports.rb index 466bfe688..5ed1ee4e5 100644 --- a/spec/factories/workbench_imports.rb +++ b/spec/factories/imports/workbench_imports.rb @@ -1,5 +1,5 @@  FactoryGirl.define do -  factory :workbench_import, class: WorkbenchImport, parent: :import do +  factory :workbench_import, class: Import::Workbench, parent: :import do      file { File.open(Rails.root.join('spec', 'fixtures', 'OFFRE_TRANSDEV_2017030112251.zip')) }    end  end diff --git a/spec/features/stop_areas_spec.rb b/spec/features/stop_areas_spec.rb index 668eb2fa3..02e853999 100644 --- a/spec/features/stop_areas_spec.rb +++ b/spec/features/stop_areas_spec.rb @@ -30,6 +30,46 @@ describe "StopAreas", :type => :feature do          expect(page).to have_content(stop_areas.first.name)          expect(page).not_to have_content(stop_areas.last.name)        end + +      context 'filtering by status' do +        before do +          stop_areas.first.activate! +          stop_areas.last.deactivate! +        end + +        describe 'updated stop areas in before block' do + +          it 'supports displaying only stop areas in creation' do +            find("#q_status_in_creation").set(true) +            click_button 'search-btn' +            expect(page).not_to have_content(stop_areas.first.name) +            expect(page).not_to have_content(stop_areas.last.name) +          end + +          it 'supports displaying only confirmed stop areas' do +            find("#q_status_confirmed").set(true) +            click_button 'search-btn' +            expect(page).to have_content(stop_areas.first.name) +            expect(page).not_to have_content(stop_areas.last.name) +          end + +          it 'supports displaying only deactivated stop areas' do +            find("#q_status_deactivated").set(true) +            click_button 'search-btn' +            expect(page).not_to have_content(stop_areas.first.name) +            expect(page).to have_content(stop_areas.last.name) +          end + +          it 'should display all stop areas if all filters are checked' do +            find("#q_status_in_creation").set(true) +            find("#q_status_confirmed").set(true) +            find("#q_status_deactivated").set(true) +            click_button 'search-btn' +            expect(page).to have_content(stop_areas.first.name) +            expect(page).to have_content(stop_areas.last.name) +          end +        end +      end      end    end diff --git a/spec/javascript/vehicle_journeys/actions_spec.js b/spec/javascript/vehicle_journeys/actions_spec.js index 9710d833c..bfbb4fb36 100644 --- a/spec/javascript/vehicle_journeys/actions_spec.js +++ b/spec/javascript/vehicle_journeys/actions_spec.js @@ -194,7 +194,7 @@ describe('when toggling arrivals', () => {  })  describe('when updating vjas time', () => {    it('should create an action to update time', () => { -    const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true +    const val = 33, subIndex = 0, index = 0, timeUnit = 'minute', isDeparture = true, isArrivalsToggled = true, enforceConsistency = false      const expectedAction = {        type: 'UPDATE_TIME',        val, @@ -202,7 +202,8 @@ describe('when updating vjas time', () => {        index,        timeUnit,        isDeparture, -      isArrivalsToggled +      isArrivalsToggled, +      enforceConsistency      }      expect(actions.updateTime(val, subIndex, index, timeUnit, isDeparture, isArrivalsToggled)).toEqual(expectedAction)    }) @@ -640,7 +641,7 @@ describe('actions.adjustSchedule', () => {          }        })        it('should do nothing', () => { -        expect(actions.adjustSchedule(action, schedule)).toEqual(schedule) +        expect(actions.adjustSchedule(action, schedule, true)).toEqual(schedule)        })      }),      context('with a delta < 0', () => { @@ -662,7 +663,7 @@ describe('actions.adjustSchedule', () => {            arrival_time: departure_time,            delta: 0          } -        expect(actions.adjustSchedule(action, schedule)).toEqual(expected) +        expect(actions.adjustSchedule(action, schedule, true)).toEqual(expected)        })      })    }), @@ -676,7 +677,7 @@ describe('actions.adjustSchedule', () => {          }        })        it('should do nothing', () => { -        expect(actions.adjustSchedule(action, schedule)).toEqual(schedule) +        expect(actions.adjustSchedule(action, schedule, true)).toEqual(schedule)        })      }),      context('with a delta < 0', () => { @@ -698,7 +699,7 @@ describe('actions.adjustSchedule', () => {            arrival_time: arrival_time,            delta: 0          } -        expect(actions.adjustSchedule(action, schedule)).toEqual(expected) +        expect(actions.adjustSchedule(action, schedule, true)).toEqual(expected)        })      })    }) diff --git a/spec/lib/model_attribute_spec.rb b/spec/lib/model_attribute_spec.rb index cdba87a90..6da740dcb 100644 --- a/spec/lib/model_attribute_spec.rb +++ b/spec/lib/model_attribute_spec.rb @@ -22,12 +22,10 @@ RSpec.describe ModelAttribute do      it "returns the list of classes of ModelAttributes in .all" do        ModelAttribute.define(:route, :name, :string)        ModelAttribute.define(:journey_pattern, :name, :string) -      ModelAttribute.define(:time_table, :start_date, :date)        expect(ModelAttribute.classes).to match_array([          'Route',          'JourneyPattern', -        'TimeTable'        ])      end    end @@ -68,13 +66,13 @@ RSpec.describe ModelAttribute do      it "returns all ModelAttributes for a given class" do        ModelAttribute.define(:route, :name, :string)        ModelAttribute.define(:route, :published_name, :string) -      ModelAttribute.define(:route, :direction, :string) +      ModelAttribute.define(:route, :wayback, :string)        ModelAttribute.define(:journey_pattern, :name, :string)        expect(ModelAttribute.methods_by_class(:route)).to match_array([          ModelAttribute.new(:route, :name, :string),          ModelAttribute.new(:route, :published_name, :string), -        ModelAttribute.new(:route, :direction, :string) +        ModelAttribute.new(:route, :wayback, :string)        ])      end    end diff --git a/spec/models/chouette/stop_area_spec.rb b/spec/models/chouette/stop_area_spec.rb index 32ee5a3a6..e35300caf 100644 --- a/spec/models/chouette/stop_area_spec.rb +++ b/spec/models/chouette/stop_area_spec.rb @@ -24,6 +24,82 @@ describe Chouette::StopArea, :type => :model do      end    end +  describe "#registration_number" do +    let(:registration_number){ nil } +    let(:registration_number_format){ nil } +    let(:stop_area_referential){ create :stop_area_referential, registration_number_format: registration_number_format} +    let(:stop_area){ build :stop_area, stop_area_referential: stop_area_referential, registration_number: registration_number} +    context "without registration_number_format on the StopAreaReferential" do +      it "should not generate a registration_number" do +        stop_area.save! +        expect(stop_area.registration_number).to_not be_present +      end + +      it "should not validate the registration_number format" do +        stop_area.registration_number = "1234455" +        expect(stop_area).to be_valid +      end + +      it "should not validate the registration_number uniqueness" do +        stop_area.registration_number = "1234455" +        create :stop_area, stop_area_referential: stop_area_referential, registration_number: stop_area.registration_number +        expect(stop_area).to be_valid +      end +    end + +    context "with a registration_number_format on the StopAreaReferential" do +      let(:registration_number_format){ "XXX" } + +      it "should generate a registration_number" do +        stop_area.save! +        expect(stop_area.registration_number).to be_present +        expect(stop_area.registration_number).to match /[A-Z]{3}/ +      end + +      context "with a previous stop_area" do +        it "should generate a registration_number" do +          create :stop_area, stop_area_referential: stop_area_referential, registration_number: "AAA" +          stop_area.save! +          expect(stop_area.registration_number).to be_present +          expect(stop_area.registration_number).to eq "AAB" +        end + +        it "should generate a registration_number" do +          create :stop_area, stop_area_referential: stop_area_referential, registration_number: "ZZZ" +          stop_area.save! +          expect(stop_area.registration_number).to be_present +          expect(stop_area.registration_number).to eq "AAA" +        end + +        it "should generate a registration_number" do +          create :stop_area, stop_area_referential: stop_area_referential, registration_number: "AAA" +          create :stop_area, stop_area_referential: stop_area_referential, registration_number: "ZZZ" +          stop_area.save! +          expect(stop_area.registration_number).to be_present +          expect(stop_area.registration_number).to eq "AAB" +        end +      end + +      it "should validate the registration_number format" do +        stop_area.registration_number = "1234455" +        expect(stop_area).to_not be_valid +        stop_area.registration_number = "ABC" +        expect(stop_area).to be_valid +        expect{ stop_area.save! }.to_not raise_error +      end + +      it "should validate the registration_number uniqueness" do +        stop_area.registration_number = "ABC" +        create :stop_area, stop_area_referential: stop_area_referential, registration_number: stop_area.registration_number +        expect(stop_area).to_not be_valid + +        stop_area.registration_number = "ABD" +        create :stop_area, registration_number: stop_area.registration_number +        expect(stop_area).to be_valid +      end +    end +  end +    # describe ".latitude" do    #   it "should accept -90 value" do    #     subject = create :stop_area, :area_type => "BoardingPosition" diff --git a/spec/models/chouette/vehicle_journey_at_stop_spec.rb b/spec/models/chouette/vehicle_journey_at_stop_spec.rb index f79d19c88..ae9823243 100644 --- a/spec/models/chouette/vehicle_journey_at_stop_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stop_spec.rb @@ -27,13 +27,13 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do      it "disallows offsets greater than DAY_OFFSET_MAX" do        expect(at_stop.day_offset_outside_range?( -        Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + 1 +        Chouette::VehicleJourneyAtStop.day_offset_max + 1        )).to be true      end      it "allows offsets between 0 and DAY_OFFSET_MAX inclusive" do        expect(at_stop.day_offset_outside_range?( -        Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX +        Chouette::VehicleJourneyAtStop.day_offset_max        )).to be false      end @@ -79,7 +79,7 @@ RSpec.describe Chouette::VehicleJourneyAtStop, type: :model do    describe "#validate" do      it "displays the proper error message when day offset exceeds the max" do -      bad_offset = Chouette::VehicleJourneyAtStop::DAY_OFFSET_MAX + 1 +      bad_offset = Chouette::VehicleJourneyAtStop.day_offset_max + 1        at_stop = build_stubbed(          :vehicle_journey_at_stop, diff --git a/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb b/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb index 69a2d5cb9..91cbf9097 100644 --- a/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb +++ b/spec/models/chouette/vehicle_journey_at_stops_day_offset_spec.rb @@ -86,5 +86,76 @@ describe Chouette::VehicleJourneyAtStop do        expect(at_stops[3].arrival_day_offset).to eq(2)        expect(at_stops[3].departure_day_offset).to eq(2)      end + +    context "with stops in a different timezone" do +      before do +        allow_any_instance_of(Chouette::VehicleJourneyAtStop).to receive(:local_time).and_wrap_original {|m, t| m.call(t - 12.hours)} +      end + +      it "should apply the TZ" do +        at_stops = [] +        [ +          ['22:30', '22:35'], +          ['01:02', '01:14'], +          ['12:02', '12:14'], +        ].each do |arrival_time, departure_time| +          at_stops << build_stubbed( +            :vehicle_journey_at_stop, +            arrival_time: arrival_time, +            departure_time: departure_time +          ) +        end +        offsetter = Chouette::VehicleJourneyAtStopsDayOffset.new(at_stops) + +        offsetter.calculate! + +        expect(at_stops[0].arrival_day_offset).to eq(0) +        expect(at_stops[0].departure_day_offset).to eq(0) + +        expect(at_stops[1].arrival_day_offset).to eq(0) +        expect(at_stops[1].departure_day_offset).to eq(0) + +        expect(at_stops[2].arrival_day_offset).to eq(1) +        expect(at_stops[2].departure_day_offset).to eq(1) +      end +    end + +    context "with stops in different timezones" do + +      it "should apply the TZ" do +        at_stops = [] + +        stop_area = create(:stop_area, time_zone: "Atlantic Time (Canada)") +        stop_point = create(:stop_point, stop_area: stop_area) +        vehicle_journey_at_stop = build_stubbed( +          :vehicle_journey_at_stop, +          stop_point: stop_point, +          arrival_time: '09:00', +          departure_time: '09:05' +        ) + +        at_stops << vehicle_journey_at_stop + +        stop_area = create(:stop_area, time_zone: "Paris") +        stop_point = create(:stop_point, stop_area: stop_area) +        vehicle_journey_at_stop = build_stubbed( +          :vehicle_journey_at_stop, +          stop_point: stop_point, +          arrival_time: '05:00', +          departure_time: '05:05' +        ) +        at_stops << vehicle_journey_at_stop +         +        offsetter = Chouette::VehicleJourneyAtStopsDayOffset.new(at_stops) + +        offsetter.calculate! + +        expect(at_stops[0].arrival_day_offset).to eq(0) +        expect(at_stops[0].departure_day_offset).to eq(0) + +        expect(at_stops[1].arrival_day_offset).to eq(1) +        expect(at_stops[1].departure_day_offset).to eq(1) +      end +    end    end  end diff --git a/spec/models/export/export_message_spec.rb b/spec/models/export/export_message_spec.rb new file mode 100644 index 000000000..61a3b6319 --- /dev/null +++ b/spec/models/export/export_message_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe Export::Message, :type => :model do +  it { should validate_presence_of(:criticity) } +  it { should belong_to(:export) } +  it { should belong_to(:resource) } +end diff --git a/spec/models/export/export_resource_spec.rb b/spec/models/export/export_resource_spec.rb new file mode 100644 index 000000000..7537cd2a8 --- /dev/null +++ b/spec/models/export/export_resource_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +RSpec.describe Export::Resource, :type => :model do +  it { should belong_to(:export) } + +  it { should enumerize(:status).in("OK", "ERROR", "WARNING", "IGNORED") } + +  it { should validate_presence_of(:name) } +  it { should validate_presence_of(:resource_type) } +  it { should validate_presence_of(:reference) } + +  describe 'states' do +    let(:export_resource) { create(:export_resource) } + +    it 'should initialize with new state' do +      expect(export_resource.status).to eq("WARNING") +    end +  end +end diff --git a/spec/models/export/export_spec.rb b/spec/models/export/export_spec.rb new file mode 100644 index 000000000..edc0788f2 --- /dev/null +++ b/spec/models/export/export_spec.rb @@ -0,0 +1,239 @@ +RSpec.describe Export::Base, type: :model do + +  it { should belong_to(:referential) } +  it { should belong_to(:workbench) } +  it { should belong_to(:parent) } + +  it { should enumerize(:status).in("aborted", "canceled", "failed", "new", "pending", "running", "successful", "warning") } + +  it { should validate_presence_of(:workbench) } +  it { should validate_presence_of(:creator) } + +  include ActionDispatch::TestProcess +  it { should allow_value(fixture_file_upload('OFFRE_TRANSDEV_2017030112251.zip')).for(:file) } +  it { should_not allow_value(fixture_file_upload('reflex_updated.xml')).for(:file).with_message(I18n.t('errors.messages.extension_whitelist_error', extension: '"xml"', allowed_types: "zip, csv, json")) } + +  let(:workgroup_export) {netex_export.parent} +  let(:workgroup_export_with_completed_steps) do +    build_stubbed( +      :workgroup_export, +      total_steps: 2, +      current_step: 2 +    ) +  end + +  let(:netex_export) do +    create( +      :netex_export +    ) +  end + +  describe ".abort_old" do +    it "changes exports older than 4 hours to aborted" do +      Timecop.freeze(Time.now) do +        old_export = create( +          :workgroup_export, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        current_export = create(:workgroup_export, status: 'pending') + +        Export::Base.abort_old + +        expect(current_export.reload.status).to eq('pending') +        expect(old_export.reload.status).to eq('aborted') +      end +    end + +    it "doesn't work on exports with a `finished_status`" do +      Timecop.freeze(Time.now) do +        export = create( +          :workgroup_export, +          status: 'successful', +          created_at: 4.hours.ago - 1.minute +        ) + +        Export::Base.abort_old + +        expect(export.reload.status).to eq('successful') +      end +    end + +    it "only works on the caller type" do +      Timecop.freeze(Time.now) do +        workgroup_export = create( +          :workgroup_export, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) +        netex_export = create( +          :netex_export, +          status: 'pending', +          created_at: 4.hours.ago - 1.minute +        ) + +        Export::Netex.abort_old + +        expect(workgroup_export.reload.status).to eq('pending') +        expect(netex_export.reload.status).to eq('aborted') +      end +    end +  end + +  describe "#destroy" do +    it "must destroy all child exports" do +      netex_export = create(:netex_export) + +      netex_export.parent.destroy + +      expect(netex_export.parent).to be_destroyed +      expect(Export::Netex.count).to eq(0) +    end + +    it "must destroy all associated Export::Messages" do +      export = create(:netex_export) +      create(:export_resource, export: export) + +      export.destroy + +      expect(Export::Resource.count).to eq(0) +    end + +    it "must destroy all associated Export::Resources" do +      export = create(:netex_export) +      create(:export_message, export: export) + +      export.destroy + +      expect(Export::Message.count).to eq(0) +    end +  end + +  describe "#notify_parent" do +    it "must call #child_change on its parent" do +      allow(netex_export).to receive(:update) + +      expect(workgroup_export).to receive(:child_change) +      netex_export.status = :foo +      netex_export.notify_parent +    end + +    it "must update the :notified_parent_at field of the child export" do +      allow(workgroup_export).to receive(:child_change) + +      Timecop.freeze(Time.now) do +        netex_export.status = :bar + +        netex_export.notify_parent +        expect(netex_export.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') +        expect(netex_export.reload.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') +      end +    end +  end + +  describe "#child_change" do +    it "calls #update_status" do +      allow(workgroup_export).to receive(:update) + +      expect(workgroup_export).to receive(:update_status) +      workgroup_export.child_change +    end +  end + +  describe "#update_status" do +    shared_examples( +      "updates :status to failed when >=1 child has failing status" +    ) do |failure_status| +      it "updates :status to failed when >=1 child has failing status" do +        workgroup_export = create(:workgroup_export) +        create( +          :netex_export, +          parent: workgroup_export, +          status: failure_status +        ) + +        workgroup_export.update_status + +        expect(workgroup_export.status).to eq('failed') +      end +    end + +    include_examples( +      "updates :status to failed when >=1 child has failing status", +      "failed" +    ) +    include_examples( +      "updates :status to failed when >=1 child has failing status", +      "aborted" +    ) +    include_examples( +      "updates :status to failed when >=1 child has failing status", +      "canceled" +    ) + +    it "updates :status to successful when all children are successful" do +      workgroup_export = create(:workgroup_export) +      exports = create_list( +        :netex_export, +        2, +        parent: workgroup_export, +        status: 'successful' +      ) + +      workgroup_export.update_status + +      expect(workgroup_export.status).to eq('successful') +    end + +    it "updates :status to failed when any child has failed" do +      workgroup_export = create(:workgroup_export) +      [ +        'failed', +        'successful' +      ].each do |status| +        create( +          :netex_export, +          parent: workgroup_export, +          status: status +        ) +      end + +      workgroup_export.update_status + +      expect(workgroup_export.status).to eq('failed') +    end + +    it "updates :status to warning when any child has warning or successful" do +      workgroup_export = create(:workgroup_export) +      [ +        'warning', +        'successful' +      ].each do |status| +        create( +          :netex_export, +          parent: workgroup_export, +          status: status +        ) +      end + +      workgroup_export.update_status + +      expect(workgroup_export.status).to eq('warning') +    end + +    it "updates :ended_at to now when status is finished" do +      workgroup_export = create(:workgroup_export) +      create( +        :netex_export, +        parent: workgroup_export, +        status: 'failed' +      ) + +      Timecop.freeze(Time.now) do +        workgroup_export.update_status + +        expect(workgroup_export.ended_at).to eq(Time.now) +      end +    end +  end +end diff --git a/spec/models/export/netex_export_spec.rb b/spec/models/export/netex_export_spec.rb new file mode 100644 index 000000000..d9cccd6ad --- /dev/null +++ b/spec/models/export/netex_export_spec.rb @@ -0,0 +1,19 @@ +RSpec.describe Export::Netex, type: [:model, :with_commit] do + +  let( :boiv_iev_uri ){  URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/exporter/new?id=#{subject.id}")} + +  before do +    allow(Thread).to receive(:new).and_yield +  end + +  context 'with referential' do +    subject{ build( :netex_export, id: random_int ) } + +    it 'will trigger the Java API' do +      with_stubbed_request(:get, boiv_iev_uri) do |request| +        with_commit{ subject.save! } +        expect(request).to have_been_requested +      end +    end +  end +end diff --git a/spec/models/export/workgroup_export_spec.rb b/spec/models/export/workgroup_export_spec.rb new file mode 100644 index 000000000..c812b2b21 --- /dev/null +++ b/spec/models/export/workgroup_export_spec.rb @@ -0,0 +1,10 @@ +RSpec.describe Export::Workgroup, type: [:model, :with_commit] do +  it { should validate_presence_of(:duration) } + +  it "should set options" do +    expect(Export::Workgroup.options).to have_key :duration +    expect(Export::Workgroup.options[:duration][:required]).to be_truthy +    expect(Export::Workgroup.options[:duration][:default_value]).to eq 90 +    expect(Export::Workgroup.options[:duration][:type]).to eq :integer +  end +end diff --git a/spec/models/export_log_message_spec.rb b/spec/models/export_log_message_spec.rb deleted file mode 100644 index 5ab32dec0..000000000 --- a/spec/models/export_log_message_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe ExportLogMessage, :type => :model do - -  # describe "#attributes" do - -  #   subject { create :export_log_message } - -  #   it "should read json stored in database" do -  #     subject.update_attribute :arguments, { "key" => "value"} -  #     expect(subject.raw_attributes).to eq({ "key" => "value"}.to_json) -  #   end - -  # end - -end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb deleted file mode 100644 index 13953078a..000000000 --- a/spec/models/export_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -# require 'spec_helper' - -# describe Export, :type => :model do - -#   subject { create :export } - -#   RSpec::Matchers.define :be_log_message do |expected| -#     match do |actual| -#       actual and expected.all? { |k,v| actual[k.to_s] == v } -#     end -#   end - -#   describe "#export" do - -#     before(:each) do -#       allow(subject).to receive_messages :exporter => double(:export => true) -#     end - -#     it "should create a ExportLogmessage :started when started" do -#       subject.export -#       expect(subject.log_messages.first).to be_log_message(:key => "started") -#     end - -#     it "should create a ExportLogmessage :completed when completed" do -#       subject.export -#       expect(subject.log_messages.last).to be_log_message(:key => "completed") -#     end - -#     it "should create a ExportLogmessage :failed when failed" do -#       pending -#       # subject.loader.stub(:export).and_raise("export failed") -#       subject.export -#       expect(subject.log_messages.last).to be_log_message(:key => "failed") -#     end - -#   end - -#   describe "#options" do - -#     it "should be empty by default" do -#       expect(subject.options).to be_empty -#     end - -#   end - -#   describe ".types" do - -#     it "should return available Export implementations" do -#       expect(Export.types).to match_array(%w{NeptuneExport CsvExport GtfsExport NetexExport KmlExport HubExport}) -#     end - -#   end - -#   describe ".new" do - -#     it "should use type attribute to create a subclass" do -#       expect(Export.new(:type => "NeptuneExport")).to be_an_instance_of(NeptuneExport) -#     end - -#   end - -#   it_behaves_like TypeIdsModelable do -#     let(:type_ids_model) { subject} -#   end - -# end diff --git a/spec/models/export_task_spec.rb b/spec/models/export_task_spec.rb deleted file mode 100644 index 1a52a6175..000000000 --- a/spec/models/export_task_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -describe ExportTask, :type => :model do - -  it { should_not validate_presence_of(:start_date) } -  it { should_not validate_presence_of(:end_date) } - -end diff --git a/spec/models/gtfs_export_spec.rb b/spec/models/gtfs_export_spec.rb index ccc98e872..0ef3660f5 100644 --- a/spec/models/gtfs_export_spec.rb +++ b/spec/models/gtfs_export_spec.rb @@ -1,33 +1,33 @@  require 'spec_helper' -describe GtfsExport, :type => :model do - -  describe "#time_zone" do - -    context "when exported data are not StopAreas" do - -      before do -        subject.references_type = "network" -      end - -      it "should be mandatory" do -        should validate_presence_of(:time_zone) -      end - -    end - -    context "when export data are StopArea" do - -      before do -        subject.references_type = "stop_area" -      end - -      it "should be mandatory" do -        should_not validate_presence_of(:time_zone) -      end - -    end - -  end - -end +# describe GtfsExport, :type => :model do +# +#   describe "#time_zone" do +# +#     context "when exported data are not StopAreas" do +# +#       before do +#         subject.references_type = "network" +#       end +# +#       it "should be mandatory" do +#         should validate_presence_of(:time_zone) +#       end +# +#     end +# +#     context "when export data are StopArea" do +# +#       before do +#         subject.references_type = "stop_area" +#       end +# +#       it "should be mandatory" do +#         should_not validate_presence_of(:time_zone) +#       end +# +#     end +# +#   end +# +# end diff --git a/spec/models/gtfs_import_spec.rb b/spec/models/gtfs_import_spec.rb index 07cc1905d..5cb69332c 100644 --- a/spec/models/gtfs_import_spec.rb +++ b/spec/models/gtfs_import_spec.rb @@ -1,6 +1,6 @@  require 'spec_helper' -describe GtfsImport, :type => :model do +describe Import::Gtfs, :type => :model do   # describe "#object_id_prefix" do diff --git a/spec/models/import_message_spec.rb b/spec/models/import/import_message_spec.rb index 2d8aac2b7..48e03a2cc 100644 --- a/spec/models/import_message_spec.rb +++ b/spec/models/import/import_message_spec.rb @@ -1,6 +1,6 @@  require 'rails_helper' -RSpec.describe ImportMessage, :type => :model do +RSpec.describe Import::Message, :type => :model do    it { should validate_presence_of(:criticity) }    it { should belong_to(:import) }    it { should belong_to(:resource) } diff --git a/spec/models/import_resource_spec.rb b/spec/models/import/import_resource_spec.rb index c88bb5dd2..7d2eab8f1 100644 --- a/spec/models/import_resource_spec.rb +++ b/spec/models/import/import_resource_spec.rb @@ -1,6 +1,6 @@  require 'rails_helper' -RSpec.describe ImportResource, :type => :model do +RSpec.describe Import::Resource, :type => :model do    it { should belong_to(:import) }    it { should enumerize(:status).in("OK", "ERROR", "WARNING", "IGNORED") } diff --git a/spec/models/import_spec.rb b/spec/models/import/import_spec.rb index 8b85f151b..102c0e1d6 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import/import_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe Import, type: :model do +RSpec.describe Import::Base, type: :model do    it { should belong_to(:referential) }    it { should belong_to(:workbench) } @@ -24,7 +24,7 @@ RSpec.describe Import, type: :model do    end    let(:netex_import) do -    build_stubbed( +    create(        :netex_import      )    end @@ -39,7 +39,7 @@ RSpec.describe Import, type: :model do          )          current_import = create(:workbench_import, status: 'pending') -        Import.abort_old +        Import::Base.abort_old          expect(current_import.reload.status).to eq('pending')          expect(old_import.reload.status).to eq('aborted') @@ -54,7 +54,7 @@ RSpec.describe Import, type: :model do            created_at: 4.hours.ago - 1.minute          ) -        Import.abort_old +        Import::Base.abort_old          expect(import.reload.status).to eq('successful')        end @@ -73,7 +73,7 @@ RSpec.describe Import, type: :model do            created_at: 4.hours.ago - 1.minute          ) -        NetexImport.abort_old +        Import::Netex.abort_old          expect(workbench_import.reload.status).to eq('pending')          expect(netex_import.reload.status).to eq('aborted') @@ -88,25 +88,25 @@ RSpec.describe Import, type: :model do        netex_import.parent.destroy        expect(netex_import.parent).to be_destroyed -      expect(NetexImport.count).to eq(0) +      expect(Import::Netex.count).to eq(0)      end -    it "must destroy all associated ImportMessages" do +    it "must destroy all associated Import::Messages" do        import = create(:import)        create(:import_resource, import: import)        import.destroy -      expect(ImportResource.count).to eq(0) +      expect(Import::Resource.count).to eq(0)      end -    it "must destroy all associated ImportResources" do +    it "must destroy all associated Import::Resources" do        import = create(:import)        create(:import_message, import: import)        import.destroy -      expect(ImportMessage.count).to eq(0) +      expect(Import::Message.count).to eq(0)      end    end @@ -115,19 +115,18 @@ RSpec.describe Import, type: :model do        allow(netex_import).to receive(:update)        expect(workbench_import).to receive(:child_change) - +      netex_import.status = :foo        netex_import.notify_parent      end      it "must update the :notified_parent_at field of the child import" do        allow(workbench_import).to receive(:child_change) - -      Timecop.freeze(DateTime.now) do -        expect(netex_import).to receive(:update).with( -          notified_parent_at: DateTime.now -        ) +      Timecop.freeze(Time.now) do +        netex_import.status = :bar          netex_import.notify_parent +        expect(netex_import.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N') +        expect(netex_import.reload.notified_parent_at.strftime('%Y-%m-%d %H:%M:%S.%3N')).to eq Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N')        end      end    end diff --git a/spec/models/import/netex_import_spec.rb b/spec/models/import/netex_import_spec.rb index 8ffeed1f4..6424fbfe1 100644 --- a/spec/models/import/netex_import_spec.rb +++ b/spec/models/import/netex_import_spec.rb @@ -1,8 +1,7 @@ -RSpec.describe NetexImport, type: [:model, :with_commit] do +RSpec.describe Import::Netex, type: [:model, :with_commit] do    let( :boiv_iev_uri ){  URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{subject.id}")} -    before do      allow(Thread).to receive(:new).and_yield    end @@ -30,4 +29,42 @@ RSpec.describe NetexImport, type: [:model, :with_commit] do      end    end +  describe "#destroy" do +    it "must destroy its associated Referential if ready: false" do +      workbench_import = create(:workbench_import) +      referential_ready_false = create(:referential, ready: false) +      referential_ready_true = create(:referential, ready: true) +      create( +        :netex_import, +        parent: workbench_import, +        referential: referential_ready_false +      ) +      create( +        :netex_import, +        parent: workbench_import, +        referential: referential_ready_true +      ) + +      workbench_import.destroy + +      expect( +        Referential.where(id: referential_ready_false.id).exists? +      ).to be false +      expect( +        Referential.where(id: referential_ready_true.id).exists? +      ).to be true +    end + +    it "doesn't try to destroy nil referentials" do +      workbench_import = create(:workbench_import) +      create( +        :netex_import, +        parent: workbench_import, +        referential: nil +      ) + +      expect { workbench_import.destroy }.not_to raise_error +    end +  end +  end diff --git a/spec/models/import_service_spec.rb b/spec/models/import_service_spec.rb deleted file mode 100644 index e7ee062d6..000000000 --- a/spec/models/import_service_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe ImportService, :type => :model do - -  let(:referential) { create(:referential, :slug => "test") } -   -  subject { ImportService.new(referential) } - -  describe '.find' do -     -    it "should build an import with a scheduled job" do       -    end - -    it "should build an import with a terminated job" do -    end -     -  end -   -end diff --git a/spec/models/import_task_spec.rb b/spec/models/import_task_spec.rb deleted file mode 100644 index 3aa006a69..000000000 --- a/spec/models/import_task_spec.rb +++ /dev/null @@ -1,196 +0,0 @@ -# require 'spec_helper' - -# describe ImportTask, :type => :model do - -#   subject { build :import_task } - -#   describe ".new" do - -#     it "should use type attribute to create a subclass" do -#       expect(ImportTask.new(:format => "Neptune")).to be_an_instance_of(NeptuneImport) -#       expect(ImportTask.new(:format => "Gtfs")).to be_an_instance_of(GtfsImport) -#       expect(ImportTask.new(:format => "Netex")).to be_an_instance_of(NetexImport) -#       expect(ImportTask.new(:format => "Csv")).to be_an_instance_of(CsvImport) - -#       expect(NeptuneImport.new).to be_an_instance_of(NeptuneImport) -#       expect(GtfsImport.new).to be_an_instance_of(GtfsImport) -#       expect(NetexImport.new).to be_an_instance_of(NetexImport) -#       expect(CsvImport.new).to be_an_instance_of(CsvImport) -#     end - -#   end - -#   describe "#delayed_import" do -#     before(:each) do -#       allow(subject).to receive_messages( :delay => double( :import => true)) -#     end -#     it "should call delay#import" do -#       expect(subject.delay).to receive( :import) -#       subject.send :delayed_import -#     end -#   end - -#   describe ".create" do -#     before(:each) do -#       allow(subject).to receive_messages( :save_resources => true ) -#     end -#     it "should call save_resource" do -#       expect(subject).to receive( :save_resources) -#       subject.send :save -#     end -#     it "should update file_path with #saved_resources" do -#       subject.send :save -#       expect(ImportTask.find( subject.id).file_path).to eq(subject.send( :saved_resources)) -#     end -#     it "should have a compliance_check_task" do -#       subject.send :save -#       expect(ImportTask.find( subject.id).compliance_check_task).not_to be_nil -#     end -#   end - -#   describe "#compliance_check_task" do -#     let(:rule_parameter_set){ Factory( :rule_parameter_set) } -#     let(:import_task){ Factory(:import_task, :rule_parameter_set_id => rule_parameter_set.id) } -#     let(:compliance_check_task){ import_task.compliance_check_task } - -#     it "should have same #referential as import_task" do -#       expect(compliance_check_task.referential).to eq(import_task.referential) -#     end - -#     it "should have same #rule_parameter_set_id as import_task" do -#       expect(compliance_check_task.rule_parameter_set_id).to eq(import_task.rule_parameter_set_id) -#     end - -#     it "should have same #user_id as import_task" do -#       expect(compliance_check_task.user_id).to eq(import_task.user_id) -#     end - -#     it "should have same #user_name as import_task" do -#       expect(compliance_check_task.user_name).to eq(import_task.user_name) -#     end -#   end - -#   describe "#file_path_extension" do -#     let(:import_task){ Factory(:import_task) } -#     context "zip file to import" do -#       before(:each) do -#         import_task.file_path = "aaa/bbb.zip" -#       end -#       it "should return zip" do -#         expect(import_task.file_path_extension).to eq("zip") -#       end -#     end -#     context "xml file to import" do -#       before(:each) do -#         import_task.file_path = "aaa/bbb.xml" -#       end -#       it "should return xml" do -#         expect(import_task.file_path_extension).to eq("xml") -#       end -#     end -#     context "csv file to import" do -#       before(:each) do -#         import_task.file_path = "aaa/bbb.csv" -#       end -#       it "should return csv" do -#         expect(import_task.file_path_extension).to eq("basic") -#       end -#     end - -#   end - -#   context "options attributes" do -#     let(:import_task){ Factory(:import_task) } -#     describe "#no_save" do -#       it "should read parameter_set['no_save']" do -#         import_task.parameter_set[ "no_save"] = "dummy" -#         expect(import_task.no_save).to eq("dummy") -#       end -#     end -#     describe "#format" do -#       it "should read parameter_set['format']" do -#         import_task.parameter_set[ "format"] = "dummy" -#         expect(import_task.format).to eq("dummy") -#       end -#     end -#     describe "#file_path" do -#       it "should read parameter_set['file_path']" do -#         import_task.parameter_set[ "file_path"] = "dummy" -#         expect(import_task.file_path).to eq("dummy") -#       end -#     end -#     describe "#no_save=" do -#       it "should read parameter_set['no_save']" do -#         import_task.no_save = "dummy" -#         expect(import_task.parameter_set[ "no_save"]).to eq(false) -#       end -#     end -#     describe "#format=" do -#       it "should read parameter_set['format']" do -#         import_task.format = "dummy" -#         expect(import_task.parameter_set[ "format"]).to eq("dummy") -#       end -#     end -#     describe "#file_path=" do -#       it "should read parameter_set['file_path']" do -#         import_task.file_path = "dummy" -#         expect(import_task.parameter_set[ "file_path"]).to eq("dummy") -#       end -#     end -#   end - -#   describe "#chouette_command" do -#     it "should be a Chouette::Command instance" do -#       expect(subject.send( :chouette_command).class).to eq(Chouette::Command) -#     end -#     it "should have schema same as referential.slug" do -#       expect(subject.send( :chouette_command).schema).to eq(subject.referential.slug) -#     end -#   end - -#   describe "#import" do -#     let(:import_task){ Factory(:import_task) } -#     let(:chouette_command) { "dummy" } -#     context "for failing import" do -#       before(:each) do -#         allow(chouette_command).to receive( :run!).and_raise( "dummy") -#         allow(import_task).to receive_messages( :chouette_command => chouette_command) -#       end -#       it "should have status 'failed'" do -#         import_task.import -#         expect(import_task.status).to eq("failed") -#       end -#       it "should have status 'failed' for compliance_check_task" do -#         import_task.import -#         expect(import_task.compliance_check_task.status).to eq("failed") -#       end -#     end -#     context "for successful import" do -#       before(:each) do -#         allow(import_task).to receive_messages( :chouette_command => double( :run! => true )) -#       end -#       it "should have status 'completed'" do -#         import_task.import -#         expect(import_task.status).to eq("completed") -#       end -#       it "should have status 'completed' for compliance_check_task" do -#         import_task.import -#         expect(import_task.status).to eq("completed") -#       end -#     end -#   end - -#   describe "#import" do -#     let(:import_task){ Factory(:import_task) } -#     let(:command_args){ "dummy" } -#     before(:each) do -#       allow(import_task).to receive_messages( :chouette_command => double( :run! => true )) -#       allow(import_task).to receive_messages( :chouette_command_args => command_args) -#     end -#     it "should call chouette_command.run! with :c => 'import', :id => id" do -#       expect(import_task.send( :chouette_command)).to receive( :run! ).with(  command_args) -#       import_task.import -#     end -#   end - -# end diff --git a/spec/models/merge_spec.rb b/spec/models/merge_spec.rb index 92f8f74b1..95181a80e 100644 --- a/spec/models/merge_spec.rb +++ b/spec/models/merge_spec.rb @@ -20,6 +20,7 @@ RSpec.describe Merge do                                        metadatas: [referential_metadata]      factor = 1 +    stop_points_positions = {}      referential.switch do        line_referential.lines.each do |line| @@ -30,12 +31,17 @@ RSpec.describe Merge do        end        referential.routes.each do |route| +        route.stop_points.each do |sp| +          sp.set_list_position 0 +        end +        route.reload.update_checksum!          factor.times do            FactoryGirl.create :journey_pattern, route: route, stop_points: route.stop_points.sample(3)          end        end        referential.journey_patterns.each do |journey_pattern| +        stop_points_positions[journey_pattern.name] = Hash[*journey_pattern.stop_points.map{|sp| [sp.stop_area_id, sp.position]}.flatten]          factor.times do            FactoryGirl.create :vehicle_journey, journey_pattern: journey_pattern, company: company          end @@ -53,6 +59,18 @@ RSpec.describe Merge do      merge = Merge.create!(workbench: referential.workbench, referentials: [referential, referential])      merge.merge! + +    output = merge.output.current +    output.switch + +    # Let's check stop_point positions are respected +    # This should be enforced by the checksum preservation though +    output.journey_patterns.each do |journey_pattern| +      journey_pattern.stop_points.each do |sp| +        expect(sp.position).to eq stop_points_positions[journey_pattern.name][sp.stop_area_id] +      end +    end +    end  end diff --git a/spec/models/netex_export_spec.rb b/spec/models/netex_export_spec.rb index 1d09fa07f..345bf4d5a 100644 --- a/spec/models/netex_export_spec.rb +++ b/spec/models/netex_export_spec.rb @@ -1,10 +1,10 @@  require 'spec_helper' -describe NetexExport, :type => :model do - -  # describe '#export_options' do -  #   subject { super().export_options } -  #   it { is_expected.to include(:format => :netex) } -  # end -   -end +# describe NetexExport, :type => :model do +# +#   # describe '#export_options' do +#   #   subject { super().export_options } +#   #   it { is_expected.to include(:format => :netex) } +#   # end +# +# end diff --git a/spec/models/netex_import_spec.rb b/spec/models/netex_import_spec.rb deleted file mode 100644 index c6051a869..000000000 --- a/spec/models/netex_import_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -RSpec.describe NetexImport, type: :model do -  describe "#destroy" do -    it "must destroy its associated Referential if ready: false" do -      workbench_import = create(:workbench_import) -      referential_ready_false = create(:referential, ready: false) -      referential_ready_true = create(:referential, ready: true) -      create( -        :netex_import, -        parent: workbench_import, -        referential: referential_ready_false -      ) -      create( -        :netex_import, -        parent: workbench_import, -        referential: referential_ready_true -      ) - -      workbench_import.destroy - -      expect( -        Referential.where(id: referential_ready_false.id).exists? -      ).to be false -      expect( -        Referential.where(id: referential_ready_true.id).exists? -      ).to be true -    end - -    it "doesn't try to destroy nil referentials" do -      workbench_import = create(:workbench_import) -      create( -        :netex_import, -        parent: workbench_import, -        referential: nil -      ) - -      expect { workbench_import.destroy }.not_to raise_error -    end -  end -end diff --git a/spec/models/simple_exporter_spec.rb b/spec/models/simple_exporter_spec.rb index 75051aeb9..a42daafe1 100644 --- a/spec/models/simple_exporter_spec.rb +++ b/spec/models/simple_exporter_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SimpleExporter do          SimpleExporter.define :foo          expect do            SimpleExporter.new(configuration_name: :test).export -        end.to raise_error +        end.to raise_error(RuntimeError)        end      end      context "with a complete configuration" do @@ -18,9 +18,9 @@ RSpec.describe SimpleExporter do        it "should define an exporter" do          expect{SimpleExporter.find_configuration(:foo)}.to_not raise_error          expect{SimpleExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error -        expect{SimpleExporter.find_configuration(:bar)}.to raise_error -        expect{SimpleExporter.new(configuration_name: :bar, filepath: "")}.to raise_error -        expect{SimpleExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error +        expect{SimpleExporter.find_configuration(:bar)}.to raise_error(RuntimeError) +        expect{SimpleExporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) +        expect{SimpleExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error(RuntimeError)          expect{SimpleExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleExporter.count}.by 1        end      end @@ -33,7 +33,7 @@ RSpec.describe SimpleExporter do              config.add_column :name              config.add_column :name            end -        end.to raise_error +        end.to raise_error(RuntimeError)        end      end    end diff --git a/spec/models/simple_importer_spec.rb b/spec/models/simple_importer_spec.rb index 5f9eb0651..6e8620d94 100644 --- a/spec/models/simple_importer_spec.rb +++ b/spec/models/simple_importer_spec.rb @@ -1,3 +1,4 @@ +# coding: utf-8  RSpec.describe SimpleImporter do    describe "#define" do      context "with an incomplete configuration" do @@ -6,7 +7,7 @@ RSpec.describe SimpleImporter do          SimpleImporter.define :foo          expect do            SimpleImporter.new(configuration_name: :foo, filepath: "").import -        end.to raise_error +        end.to raise_error(RuntimeError)        end      end      context "with a complete configuration" do @@ -20,8 +21,8 @@ RSpec.describe SimpleImporter do          expect{SimpleImporter.find_configuration(:foo)}.to_not raise_error          expect{SimpleImporter.new(configuration_name: :foo, filepath: "")}.to_not raise_error          expect{SimpleImporter.new(configuration_name: :foo, filepath: "").import}.to_not raise_error -        expect{SimpleImporter.find_configuration(:bar)}.to raise_error -        expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error +        expect{SimpleImporter.find_configuration(:bar)}.to raise_error(RuntimeError) +        expect{SimpleImporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError)          expect{SimpleImporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleImporter.count}.by 1        end      end @@ -45,11 +46,12 @@ RSpec.describe SimpleImporter do          config.add_column :street_name          config.add_column :stop_area_referential, value: stop_area_referential          config.add_value  :kind, :commercial +        config.add_value  :status, :confirmed        end      end      it "should import the given file" do -      expect{importer.import verbose: true}.to change{Chouette::StopArea.count}.by 1 +      expect{importer.import verbose: false}.to change{Chouette::StopArea.count}.by 1        expect(importer.status).to eq "success"        stop = Chouette::StopArea.last        expect(stop.name).to eq "Nom du Stop" @@ -141,8 +143,8 @@ RSpec.describe SimpleImporter do      end      context "with a custom behaviour" do -      let!(:present){ create :stop_area, name: "Nom du Stop", stop_area_referential: stop_area_referential } -      let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential } +      let!(:present){ create :stop_area, name: "Nom du Stop", stop_area_referential: stop_area_referential, status: :confirmed } +      let!(:missing){ create :stop_area, name: "Another", stop_area_referential: stop_area_referential, status: :confirmed }        before(:each){          importer.configure do |config|            config.before do |importer| diff --git a/spec/models/simple_interfaces_group_spec.rb b/spec/models/simple_interfaces_group_spec.rb new file mode 100644 index 000000000..0b6d360de --- /dev/null +++ b/spec/models/simple_interfaces_group_spec.rb @@ -0,0 +1,31 @@ +RSpec.describe SimpleInterfacesGroup do +  context "with successful interfaces" do +    before do +      create :stop_area +      SimpleExporter.define :test_1 do |config| +        config.collection = Chouette::StopArea.all +        config.key = "name" +        config.add_column :name +      end + +      SimpleExporter.define :test_2 do |config| +        config.collection = Chouette::StopArea.all +        config.key = "name" +        config.add_column :lat, attribute: :latitude +      end +    end + +    it "should run all interfaces" do +      test_1 = SimpleExporter.new(configuration_name: :test_1, filepath: "tmp/test1.csv") +      test_2 = SimpleExporter.new(configuration_name: :test_2, filepath: "tmp/test1.csv") + +      expect(test_1).to receive(:export).and_call_original +      expect(test_2).to receive(:export).and_call_original + +      group = SimpleInterfacesGroup.new "group" +      group.add_interface test_1, "Test 1", :export +      group.add_interface test_2, "Test 2", :export +      group.run +    end +  end +end diff --git a/spec/models/stop_area_referential_spec.rb b/spec/models/stop_area_referential_spec.rb index dd2bdce20..d68b5b809 100644 --- a/spec/models/stop_area_referential_spec.rb +++ b/spec/models/stop_area_referential_spec.rb @@ -8,4 +8,9 @@ RSpec.describe StopAreaReferential, :type => :model do    it { is_expected.to have_many(:stop_area_referential_syncs) }    it { is_expected.to have_many(:workbenches) }    it { should validate_presence_of(:objectid_format) } +  it { should allow_value('').for(:registration_number_format) } +  it { should allow_value('X').for(:registration_number_format) } +  it { should allow_value('XXXXX').for(:registration_number_format) } +  it { should_not allow_value('123').for(:registration_number_format) } +  it { should_not allow_value('ABC').for(:registration_number_format) }  end diff --git a/spec/models/workgroup_spec.rb b/spec/models/workgroup_spec.rb index ac8d3fc98..97fff3d86 100644 --- a/spec/models/workgroup_spec.rb +++ b/spec/models/workgroup_spec.rb @@ -6,6 +6,8 @@ RSpec.describe Workgroup, type: :model do      it{ should have_many(:workbenches) }      it{ should validate_uniqueness_of(:name) } +    it{ should validate_uniqueness_of(:stop_area_referential_id) } +    it{ should validate_uniqueness_of(:line_referential_id) }      it 'is not valid without a stop_area_referential' do        workgroup.stop_area_referential_id = nil diff --git a/spec/requests/api/v1/netex_import_spec.rb b/spec/requests/api/v1/netex_import_spec.rb index 8597c1d32..14dac9a25 100644 --- a/spec/requests/api/v1/netex_import_spec.rb +++ b/spec/requests/api/v1/netex_import_spec.rb @@ -1,4 +1,4 @@ -RSpec.describe "NetexImport", type: :request do +RSpec.describe "Import::Netex", type: :request do    describe 'POST netex_imports' do @@ -39,7 +39,7 @@ RSpec.describe "NetexImport", type: :request do          post_request.(netex_import: legal_attributes)          expect( response ).to be_success          expect( json_response_body ).to eq( -          'id'             => NetexImport.last.id, +          'id'             => Import::Netex.last.id,            'referential_id' => Referential.last.id,            'workbench_id'   => workbench.id          ) @@ -51,7 +51,7 @@ RSpec.describe "NetexImport", type: :request do          create(:line, objectid: 'STIF:CODIFLIGNE:Line:C00108', line_referential: workbench.line_referential)          create(:line, objectid: 'STIF:CODIFLIGNE:Line:C00109', line_referential: workbench.line_referential) -        expect{ post_request.(netex_import: legal_attributes) }.to change{NetexImport.count}.by(1) +        expect{ post_request.(netex_import: legal_attributes) }.to change{Import::Netex.count}.by(1)        end        it 'creates a correct Referential', pending: 'see #5073' do @@ -96,7 +96,7 @@ RSpec.describe "NetexImport", type: :request do            end            it 'does not create an Import object' do -            expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import.count} +            expect{ post_request.(netex_import: illegal_attributes) }.not_to change{Import::Base.count}            end            it 'might not create a referential' do diff --git a/spec/services/parent_notifier_spec.rb b/spec/services/parent_notifier_spec.rb index ecf508fcd..d2dc6b184 100644 --- a/spec/services/parent_notifier_spec.rb +++ b/spec/services/parent_notifier_spec.rb @@ -20,7 +20,7 @@ RSpec.describe ParentNotifier do          expect(netex_import).to receive(:notify_parent)        end -      ParentNotifier.new(Import).notify_when_finished(netex_imports) +      ParentNotifier.new(Import::Base).notify_when_finished(netex_imports)      end      it "doesn't call #notify_parent if its `notified_parent_at` is set" do @@ -33,7 +33,7 @@ RSpec.describe ParentNotifier do        expect(netex_import).not_to receive(:notify_parent) -      ParentNotifier.new(Import).notify_when_finished +      ParentNotifier.new(Import::Base).notify_when_finished      end    end @@ -46,8 +46,10 @@ RSpec.describe ParentNotifier do          notified_parent_at: nil        ) +      Import::Base.where(id: netex_import).update_all notified_parent_at: nil +        expect( -        ParentNotifier.new(Import).objects_pending_notification +        ParentNotifier.new(Import::Base).objects_pending_notification        ).to eq([netex_import])      end @@ -55,7 +57,7 @@ RSpec.describe ParentNotifier do        create(:import, parent: nil)        expect( -        ParentNotifier.new(Import).objects_pending_notification +        ParentNotifier.new(Import::Base).objects_pending_notification        ).to be_empty      end @@ -70,7 +72,7 @@ RSpec.describe ParentNotifier do        end        expect( -        ParentNotifier.new(Import).objects_pending_notification +        ParentNotifier.new(Import::Base).objects_pending_notification        ).to be_empty      end @@ -83,7 +85,7 @@ RSpec.describe ParentNotifier do        )        expect( -        ParentNotifier.new(Import).objects_pending_notification +        ParentNotifier.new(Import::Base).objects_pending_notification        ).to be_empty      end    end diff --git a/spec/support/permissions.rb b/spec/support/permissions.rb index 95afd6c1c..825e44725 100644 --- a/spec/support/permissions.rb +++ b/spec/support/permissions.rb @@ -17,6 +17,7 @@ module Support          connection_links          calendars          footnotes +        exports          imports          merges          journey_patterns | 
