diff options
| author | Alban Peignier | 2017-12-27 22:47:52 +0100 | 
|---|---|---|
| committer | GitHub | 2017-12-27 22:47:52 +0100 | 
| commit | 5826d342ae08b904ed23c45fd96066a348f04596 (patch) | |
| tree | 7879c1dd3a496fb90f45efdf0ac79f0881f0c953 | |
| parent | b754b931eedfdd46a2ce32f1ca19683b6b79931c (diff) | |
| parent | 9854027e72c5084cc9183b1fc04816d7a914c1e5 (diff) | |
| download | chouette-core-5826d342ae08b904ed23c45fd96066a348f04596.tar.bz2 | |
Merge pull request #183 from af83/5407-add-purchase-windows-to-vehicle-journeys
Add purchase windows to vehicle journeys. Refs #5407
35 files changed, 662 insertions, 45 deletions
| diff --git a/app/assets/stylesheets/typography/_sboiv.sass b/app/assets/stylesheets/typography/_sboiv.sass index 0ed2890fa..f0943f843 100644 --- a/app/assets/stylesheets/typography/_sboiv.sass +++ b/app/assets/stylesheets/typography/_sboiv.sass @@ -52,6 +52,8 @@    &.sb-5x      font-size: 5em +  &.sb-strong +    font-weight: bold  .sb-ZDLR:before    content: '\e904' diff --git a/app/controllers/autocomplete_purchase_windows_controller.rb b/app/controllers/autocomplete_purchase_windows_controller.rb new file mode 100644 index 000000000..70dc5a346 --- /dev/null +++ b/app/controllers/autocomplete_purchase_windows_controller.rb @@ -0,0 +1,12 @@ +class AutocompletePurchaseWindowsController < ChouetteController +  respond_to :json, :only => [:index] + +  requires_feature :purchase_windows + +  include ReferentialSupport + +  protected +  def collection +    @purchase_windows = referential.purchase_windows.search(params[:q]).result.paginate(page: params[:page]) +  end +end diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index 454f9c3dd..4bf65a084 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -164,6 +164,7 @@ class VehicleJourneysController < ChouetteController    end    def user_permissions +    @features = Hash[*current_organisation.features.map{|f| [f, true]}.flatten].to_json      policy = policy(:vehicle_journey)      @perms =        %w{create destroy update}.inject({}) do | permissions, action | diff --git a/app/javascript/packs/vehicle_journeys/index.js b/app/javascript/packs/vehicle_journeys/index.js index 38431af1d..53c5d5417 100644 --- a/app/javascript/packs/vehicle_journeys/index.js +++ b/app/javascript/packs/vehicle_journeys/index.js @@ -23,6 +23,7 @@ var initialState = {    filters: {      selectedJourneyPatterns : selectedJP,      policy: window.perms, +    features: window.features,      toggleArrivals: false,      queryString: '',      query: { @@ -54,7 +55,7 @@ var initialState = {    },    status: { -    fetchSuccess: true, +    fetchSuccess: false,      isFetching: false    },    vehicleJourneys: [], @@ -99,4 +100,4 @@ render(      <App />    </Provider>,    document.getElementById('vehicle_journeys_wip') -)
\ No newline at end of file +) diff --git a/app/javascript/vehicle_journeys/actions/index.js b/app/javascript/vehicle_journeys/actions/index.js index d4d824cf5..202c09440 100644 --- a/app/javascript/vehicle_journeys/actions/index.js +++ b/app/javascript/vehicle_journeys/actions/index.js @@ -100,6 +100,31 @@ const actions = {      vehicleJourneys,      timetables    }), +  openPurchaseWindowsEditModal : (vehicleJourneys) => ({ +    type : 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL', +    vehicleJourneys +  }), +  selectPurchaseWindowsModal: (selectedWindow) =>({ +    type: 'SELECT_PURCHASE_WINDOW_MODAL', +    selectedItem:{ +      id: selectedWindow.id, +      name: selectedWindow.name, +      color: selectedWindow.color, +      objectid: selectedWindow.objectid +    } +  }), +  addSelectedPurchaseWindow: () => ({ +    type: 'ADD_SELECTED_PURCHASE_WINDOW' +  }), +  deletePurchaseWindowsModal : (purchaseWindow) => ({ +    type : 'DELETE_PURCHASE_WINDOW_MODAL', +    purchaseWindow +  }), +  editVehicleJourneyPurchaseWindows : (vehicleJourneys, purchase_windows) => ({ +    type: 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS', +    vehicleJourneys, +    purchase_windows +  }),    openShiftModal : () => ({      type : 'SHIFT_VEHICLEJOURNEY_MODAL'    }), @@ -314,6 +339,7 @@ const actions = {            let val            for (val of json.vehicle_journeys){              var timeTables = [] +            var purchaseWindows = []              let tt              for (tt of val.time_tables){                timeTables.push({ @@ -323,6 +349,14 @@ const actions = {                  color: tt.color                })              } +            for (tt of val.purchase_windows){ +              purchaseWindows.push({ +                objectid: tt.objectid, +                name: tt.name, +                id: tt.id, +                color: tt.color +              }) +            }              let vjasWithDelta = val.vehicle_journey_at_stops.map((vjas, i) => {                actions.fillEmptyFields(vjas)                return actions.getDelta(vjas) @@ -334,6 +368,7 @@ const actions = {                short_id: val.short_id,                footnotes: val.footnotes,                time_tables: timeTables, +              purchase_windows: purchaseWindows,                vehicle_journey_at_stops: vjasWithDelta,                deletable: false,                selected: false, diff --git a/app/javascript/vehicle_journeys/components/Filters.js b/app/javascript/vehicle_journeys/components/Filters.js index db6707520..3bc4f7ff7 100644 --- a/app/javascript/vehicle_journeys/components/Filters.js +++ b/app/javascript/vehicle_journeys/components/Filters.js @@ -33,6 +33,7 @@ export default function Filters({filters, pagination, onFilter, onResetFilters,                  onSelect2Timetable={onSelect2Timetable}                  hasRoute={true}                  chunkURL={("/autocomplete_time_tables.json?route_id=" + String(window.route_id))} +                searchKey={"comment_or_objectid_cont_any"}                  filters={filters}                  isFilter={true}                  /> @@ -165,4 +166,4 @@ Filters.propTypes = {    onSelect2Timetable: PropTypes.func.isRequired,    onSelect2JourneyPattern: PropTypes.func.isRequired,    onSelect2VehicleJourney: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js index e8c27f92e..8bab5baa9 100644 --- a/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/SaveVehicleJourneys.js @@ -14,16 +14,27 @@ export default class SaveVehicleJourneys extends Component{          <div className='row mt-md'>            <div className='col-lg-12 text-right'>              <form className='vehicle_journeys formSubmitr ml-xs' onSubmit={e => {e.preventDefault()}}> -              <button -                className='btn btn-default' -                type='button' -                onClick={e => { -                  e.preventDefault() -                  this.props.editMode ? this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys) : this.props.onEnterEditMode() -                }} -              > -                {this.props.editMode ? "Valider" : "Editer"} -              </button> +              <div className="btn-group sticky-actions"> +                <button +                  className={'btn ' + (this.props.editMode ? 'btn-success' : 'btn-default') + (this.props.status.fetchSuccess ? '' : ' disabled')} +                  type='button' +                  onClick={e => { +                    e.preventDefault() +                    this.props.editMode ? this.props.onSubmitVehicleJourneys(this.props.dispatch, this.props.vehicleJourneys) : this.props.onEnterEditMode() +                  }} +                > +                  {this.props.editMode ? "Valider" : "Editer"} +                </button> +                {this.props.editMode && <button +                    className='btn btn-default' +                    type='button' +                    onClick={e => { +                      e.preventDefault() +                      this.props.onExitEditMode() +                    }} +                  > Annuler </button> +                } +              </div>              </form>            </div>          </div> @@ -38,5 +49,6 @@ SaveVehicleJourneys.propTypes = {    status: PropTypes.object.isRequired,    filters: PropTypes.object.isRequired,    onEnterEditMode: PropTypes.func.isRequired, +  onExitEditMode: PropTypes.func.isRequired,    onSubmitVehicleJourneys: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/Tools.js b/app/javascript/vehicle_journeys/components/Tools.js index 1ef576529..d6e04f00e 100644 --- a/app/javascript/vehicle_journeys/components/Tools.js +++ b/app/javascript/vehicle_journeys/components/Tools.js @@ -7,6 +7,7 @@ import DuplicateVehicleJourney from '../containers/tools/DuplicateVehicleJourney  import EditVehicleJourney from '../containers/tools/EditVehicleJourney'  import NotesEditVehicleJourney from '../containers/tools/NotesEditVehicleJourney'  import TimetablesEditVehicleJourney from '../containers/tools/TimetablesEditVehicleJourney' +import PurchaseWindowsEditVehicleJourney from '../containers/tools/PurchaseWindowsEditVehicleJourney'  export default class Tools extends Component { @@ -20,18 +21,26 @@ export default class Tools extends Component {      return this.props.filters.policy[`vehicle_journeys.${key}`]    } +  hasFeature(key) { +    // Check if the organisation has the given feature +    return this.props.filters.features[key] +  } +    render() {      let { vehicleJourneys, onCancelSelection, editMode } = this.props      return (        <div className='select_toolbox'>          <ul> -          <AddVehicleJourney disabled={this.hasPolicy("create") && !editMode} /> -          <DuplicateVehicleJourney disabled={this.hasPolicy("create") && this.hasPolicy("update") && !editMode}/> -          <ShiftVehicleJourney disabled={this.hasPolicy("update") && !editMode}/> +          <AddVehicleJourney disabled={!this.hasPolicy("create") || !editMode} /> +          <DuplicateVehicleJourney disabled={!this.hasPolicy("create") || !this.hasPolicy("update") || !editMode}/> +          <ShiftVehicleJourney disabled={!this.hasPolicy("update") || !editMode}/>            <EditVehicleJourney disabled={!this.hasPolicy("update")}/>            <TimetablesEditVehicleJourney disabled={!this.hasPolicy("update")}/> +          { this.hasFeature('purchase_windows') && +            <PurchaseWindowsEditVehicleJourney disabled={!this.hasPolicy("update")}/> +          }            <NotesEditVehicleJourney disabled={!this.hasPolicy("update")}/> -          <DeleteVehicleJourneys disabled={this.hasPolicy("destroy") && !editMode}/> +          <DeleteVehicleJourneys disabled={!this.hasPolicy("destroy") || !editMode}/>          </ul>          <span className='info-msg'>{actions.getSelected(vehicleJourneys).length} course(s) sélectionnée(s)</span> diff --git a/app/javascript/vehicle_journeys/components/VehicleJourney.js b/app/javascript/vehicle_journeys/components/VehicleJourney.js index 2ce257284..1e62444ef 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourney.js @@ -17,6 +17,10 @@ export default class VehicleJourney extends Component {      return bool    } +  hasFeature(key) { +    return this.props.filters.features[key] +  } +    timeTableURL(tt) {      let refURL = window.location.pathname.split('/', 3).join('/')      let ttURL = refURL + '/time_tables/' + tt.id @@ -26,6 +30,15 @@ export default class VehicleJourney 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 : '')}}></span></a> +    ) +  } +    columnHasDelta() {      let a = []      this.props.value.vehicle_journey_at_stops.map((vj, i) => { @@ -44,11 +57,16 @@ export default class VehicleJourney extends Component {    render() {      this.previousCity = undefined -    let {time_tables} = this.props.value +    let {time_tables, purchase_windows} = this.props.value      return (        <div className={'t2e-item' + (this.props.value.deletable ? ' disabled' : '') + (this.props.value.errors ? ' has-error': '')}> -        <div className='th'> +        <div +          className='th' +          onClick={(e) => +            ($(e.target).parents("a").length == 0) && this.props.editMode && this.props.onSelectVehicleJourney(this.props.index) +          } +          >            <div className='strong mb-xs'>{this.props.value.short_id || '-'}</div>            <div>{this.props.value.journey_pattern.short_id || '-'}</div>            <div> @@ -57,6 +75,14 @@ export default class VehicleJourney extends Component {              )}              {time_tables.length > 3 && <span className='vj_tt'> + {time_tables.length - 3}</span>}            </div> +          { this.hasFeature('purchase_windows') && +            <div> +              {purchase_windows.slice(0,3).map((tt, i)=> +                <span key={i} className='vj_tt'>{this.purchaseWindowURL(tt)}</span> +              )} +              {purchase_windows.length > 3 && <span className='vj_tt'> + {purchase_windows.length - 3}</span>} +            </div> +          }            <div className={(this.props.value.deletable ? 'disabled ' : '') + 'checkbox'}>              <input                id={this.props.index} diff --git a/app/javascript/vehicle_journeys/components/VehicleJourneys.js b/app/javascript/vehicle_journeys/components/VehicleJourneys.js index 6bce9766b..e16d03f90 100644 --- a/app/javascript/vehicle_journeys/components/VehicleJourneys.js +++ b/app/javascript/vehicle_journeys/components/VehicleJourneys.js @@ -12,6 +12,10 @@ export default class VehicleJourneys extends Component {      this.props.onLoadFirstPage(this.props.filters)    } +  hasFeature(key) { +    return this.props.filters.features[key] +  } +    componentDidUpdate(prevProps, prevState) {      if(this.props.status.isFetching == false){        $('.table-2entries').each(function() { @@ -113,6 +117,7 @@ export default class VehicleJourneys extends Component {                    <div className='strong mb-xs'>ID course</div>                    <div>ID mission</div>                    <div>Calendriers</div> +                  { this.hasFeature('purchase_windows') && <div>Calendriers Commerciaux</div> }                  </div>                  {this.props.stopPointsList.map((sp, i) =>{                    return ( @@ -132,6 +137,7 @@ export default class VehicleJourneys extends Component {                        index={index}                        editMode={this.props.editMode}                        filters={this.props.filters} +                      features={this.props.features}                        onUpdateTime={this.props.onUpdateTime}                        onSelectVehicleJourney={this.props.onSelectVehicleJourney}                        /> @@ -153,4 +159,4 @@ VehicleJourneys.propTypes = {    onLoadFirstPage: PropTypes.func.isRequired,    onUpdateTime: PropTypes.func.isRequired,    onSelectVehicleJourney: PropTypes.func.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js new file mode 100644 index 000000000..d61c7a34b --- /dev/null +++ b/app/javascript/vehicle_journeys/components/tools/PurchaseWindowsEditVehicleJourney.js @@ -0,0 +1,152 @@ +import React, { PropTypes, Component } from 'react' +import actions from '../../actions' +import TimetableSelect2 from './select2s/TimetableSelect2' + +export default class PurchaseWindowsEditVehicleJourney extends Component { +  constructor(props) { +    super(props) +    this.handleSubmit = this.handleSubmit.bind(this) +    this.purchaseWindowURL = this.purchaseWindowURL.bind(this) +  } + +  handleSubmit() { +    this.props.onShoppingWindowsEditVehicleJourney(this.props.modal.modalProps.vehicleJourneys, this.props.modal.modalProps.purchase_windows) +    this.props.onModalClose() +    $('#PurchaseWindowsEditVehicleJourneyModal').modal('hide') +  } + +  purchaseWindowURL(tt) { +    let refURL = window.location.pathname.split('/', 3).join('/') +    return refURL + '/purchase_windows/' + tt.id +  } + +  render() { +    if(this.props.status.isFetching == true) { +      return false +    } +    if(this.props.status.fetchSuccess == true) { +      return ( +        <li className='st_action'> +          <button +            type='button' +            disabled={(actions.getSelected(this.props.vehicleJourneys).length < 1 || this.props.disabled)} +            data-toggle='modal' +            data-target='#PurchaseWindowsEditVehicleJourneyModal' +            onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))} +            title='Calendriers commerciaux' +          > +            <span className='sb sb-purchase_window sb-strong'></span> +          </button> + +          <div className={ 'modal fade ' + ((this.props.modal.type == 'duplicate') ? 'in' : '') } id='PurchaseWindowsEditVehicleJourneyModal'> +            <div className='modal-container'> +              <div className='modal-dialog'> +                <div className='modal-content'> +                  <div className='modal-header'> +                    <h4 className='modal-title'>Calendriers commerciaux associés</h4> +                    <span type="button" className="close modal-close" data-dismiss="modal">×</span> +                  </div> + +                  {(this.props.modal.type == 'purchase_windows_edit') && ( +                    <form> +                      <div className='modal-body'> +                        <div className='row'> +                          <div className='col-lg-12'> +                            <div className='subform'> +                              <div className='nested-head'> +                                <div className='wrapper'> +                                  <div> +                                    <div className='form-group'> +                                      <label className='control-label'>{this.props.modal.modalProps.purchase_windows.length == 0 ? "Aucun calendrier commercial associé" : "Calendriers commerciaux associés"}</label> +                                    </div> +                                  </div> +                                  <div></div> +                                </div> +                              </div> +                              {this.props.modal.modalProps.purchase_windows.map((tt, i) => +                                <div className='nested-fields' key={i}> +                                  <div className='wrapper'> +                                    <div> <a href={this.purchaseWindowURL(tt)} target="_blank"> +                                      <span className="fa fa-circle mr-xs" style={{color: tt.color}}></span> +                                      {tt.name} +                                    </a> </div> +                                    { +                                      this.props.editMode && +                                      <div> +                                        <a +                                          href='#' +                                          title='Supprimer' +                                          className='fa fa-trash remove_fields' +                                          style={{ height: 'auto', lineHeight: 'normal' }} +                                          onClick={(e) => { +                                            e.preventDefault() +                                            this.props.onDeleteCalendarModal(tt) +                                          }} +                                        ></a> +                                      </div> +                                    } +                                  </div> +                                </div> +                              )} +                              { +                                this.props.editMode && +                                <div className='nested-fields'> +                                  <div className='wrapper'> +                                    <div> +                                      <TimetableSelect2 +                                        onSelect2Timetable={this.props.onSelect2Timetable} +                                        chunkURL={'/autocomplete_purchase_windows.json'} +                                        searchKey={"name_or_objectid_cont_any"} +                                        isFilter={false} +                                      /> +                                    </div> +                                  </div> +                                </div> +                              } +                            </div> +                          </div> +                        </div> +                      </div> +                      { +                        this.props.editMode && +                        <div className='modal-footer'> +                          <button +                            className='btn btn-link' +                            data-dismiss='modal' +                            type='button' +                            onClick={this.props.onModalClose} +                          > +                            Annuler +                          </button> +                          <button +                            className='btn btn-primary' +                            type='button' +                            onClick={this.handleSubmit} +                          > +                            Valider +                          </button> +                        </div> +                      } +                    </form> +                  )} + +                </div> +              </div> +            </div> +          </div> +        </li> +      ) +    } else { +      return false +    } +  } +} + +PurchaseWindowsEditVehicleJourney.propTypes = { +  onOpenCalendarsEditModal: PropTypes.func.isRequired, +  onModalClose: PropTypes.func.isRequired, +  onShoppingWindowsEditVehicleJourney: PropTypes.func.isRequired, +  onDeleteCalendarModal: PropTypes.func.isRequired, +  onSelect2Timetable: PropTypes.func.isRequired, +  disabled: PropTypes.bool.isRequired +} diff --git a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js index 6629135dd..56f80ebb5 100644 --- a/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js +++ b/app/javascript/vehicle_journeys/components/tools/TimetablesEditVehicleJourney.js @@ -33,6 +33,7 @@ export default class TimetablesEditVehicleJourney extends Component {              data-toggle='modal'              data-target='#CalendarsEditVehicleJourneyModal'              onClick={() => this.props.onOpenCalendarsEditModal(actions.getSelected(this.props.vehicleJourneys))} +            title='Calendriers'            >              <span className='fa fa-calendar'></span>            </button> @@ -67,7 +68,7 @@ export default class TimetablesEditVehicleJourney extends Component {                                    <div className='wrapper'>                                      <div> <a href={this.timeTableURL(tt)} target="_blank">{tt.comment}</a> </div>                                      { -                                      this.props.editMode &&  +                                      this.props.editMode &&                                        <div>                                          <a                                            href='#' @@ -85,13 +86,14 @@ export default class TimetablesEditVehicleJourney extends Component {                                  </div>                                )}                                { -                                this.props.editMode &&  +                                this.props.editMode &&                                  <div className='nested-fields'>                                    <div className='wrapper'>                                      <div>                                        <TimetableSelect2                                          onSelect2Timetable={this.props.onSelect2Timetable}                                          chunkURL={'/autocomplete_time_tables.json'} +                                        searchKey={"comment_or_objectid_cont_any"}                                          isFilter={false}                                        />                                      </div> @@ -103,7 +105,7 @@ export default class TimetablesEditVehicleJourney extends Component {                          </div>                        </div>                        { -                        this.props.editMode &&  +                        this.props.editMode &&                          <div className='modal-footer'>                            <button                              className='btn btn-link' @@ -144,4 +146,4 @@ TimetablesEditVehicleJourney.propTypes = {    onDeleteCalendarModal: PropTypes.func.isRequired,    onSelect2Timetable: PropTypes.func.isRequired,    disabled: PropTypes.bool.isRequired -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js index 19c183839..eb8651be2 100644 --- a/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js +++ b/app/javascript/vehicle_journeys/components/tools/select2s/TimetableSelect2.js @@ -31,12 +31,10 @@ export default class BSelect4 extends Component {              url: origin + path + this.props.chunkURL,              dataType: 'json',              delay: '500', -            data: function(params) { -              return { -                q: { -                  comment_or_objectid_cont_any: params.term -                } -              }; +            data: (params) => { +              let q = {} +              q[this.props.searchKey] = params.term +              return {q}              },              processResults: function(data, params) {                return { @@ -44,7 +42,7 @@ export default class BSelect4 extends Component {                    item => _.assign(                      {},                      item, -                    {text: '<strong>' + "<span class='fa fa-circle' style='color:" + (item.color ? item.color : '#4B4B4B') + "'></span> " + item.comment + ' - ' + item.short_id + '</strong><br/><small>' + (item.day_types ? item.day_types.match(/[A-Z]?[a-z]+/g).join(', ') : "") + '</small>'} +                    {text: '<strong>' + "<span class='fa fa-circle' style='color:" + (item.color ? item.color : '#4B4B4B') + "'></span> " + (item.comment || item.name) + ' - ' + item.short_id + '</strong><br/><small>' + (item.day_types ? item.day_types.match(/[A-Z]?[a-z]+/g).join(', ') : "") + '</small>'}                    )                  )                }; @@ -62,4 +60,4 @@ export default class BSelect4 extends Component {  const formatRepo = (props) => {    if(props.text) return props.text -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js index 18f9e994e..f5f879ed8 100644 --- a/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js +++ b/app/javascript/vehicle_journeys/containers/SaveVehicleJourneys.js @@ -17,6 +17,10 @@ const mapDispatchToProps = (dispatch) => {      onEnterEditMode: () => {        dispatch(actions.enterEditMode())      }, +    onExitEditMode: () => { +      dispatch(actions.cancelSelection()) +      dispatch(actions.exitEditMode()) +    },      onSubmitVehicleJourneys: (next, state) => {        actions.submitVehicleJourneys(dispatch, state, next)      } diff --git a/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js b/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js new file mode 100644 index 000000000..3fef44489 --- /dev/null +++ b/app/javascript/vehicle_journeys/containers/tools/PurchaseWindowsEditVehicleJourney.js @@ -0,0 +1,38 @@ +import actions from '../../actions' +import { connect } from 'react-redux' +import PurchaseWindowsEditVehicleJourneyComponent from '../../components/tools/PurchaseWindowsEditVehicleJourney' + +const mapStateToProps = (state, ownProps) => { +  return { +    editMode: state.editMode, +    modal: state.modal, +    vehicleJourneys: state.vehicleJourneys, +    status: state.status, +    disabled: ownProps.disabled +  } +} + +const mapDispatchToProps = (dispatch) => { +  return { +    onModalClose: () =>{ +      dispatch(actions.closeModal()) +    }, +    onOpenCalendarsEditModal: (vehicleJourneys) =>{ +      dispatch(actions.openPurchaseWindowsEditModal(vehicleJourneys)) +    }, +    onDeleteCalendarModal: (timetable) => { +      dispatch(actions.deletePurchaseWindowsModal(timetable)) +    }, +    onShoppingWindowsEditVehicleJourney: (vehicleJourneys, timetables) =>{ +      dispatch(actions.editVehicleJourneyPurchaseWindows(vehicleJourneys, timetables)) +    }, +    onSelect2Timetable: (e) =>{ +      dispatch(actions.selectPurchaseWindowsModal(e.params.data)) +      dispatch(actions.addSelectedPurchaseWindow()) +    } +  } +} + +const PurchaseWindowsEditVehicleJourney = connect(mapStateToProps, mapDispatchToProps)(PurchaseWindowsEditVehicleJourneyComponent) + +export default PurchaseWindowsEditVehicleJourney diff --git a/app/javascript/vehicle_journeys/reducers/modal.js b/app/javascript/vehicle_journeys/reducers/modal.js index 57f54a144..862e27e1b 100644 --- a/app/javascript/vehicle_journeys/reducers/modal.js +++ b/app/javascript/vehicle_journeys/reducers/modal.js @@ -40,7 +40,6 @@ export default function modal(state = {}, action) {      case 'EDIT_CALENDARS_VEHICLEJOURNEY_MODAL':        vehicleJourneysModal = JSON.parse(JSON.stringify(action.vehicleJourneys))        let uniqTimetables = [] -      let timetable = {}        vehicleJourneysModal.map((vj, i) => {          vj.time_tables.map((tt, j) =>{            if(!(_.find(uniqTimetables, tt))){ @@ -56,6 +55,24 @@ export default function modal(state = {}, action) {          },          confirmModal: {}        } +    case 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL': +      var vehicleJourneys = JSON.parse(JSON.stringify(action.vehicleJourneys)) +      let uniqPurchaseWindows = [] +      vehicleJourneys.map((vj, i) => { +        vj.purchase_windows.map((pw, j) =>{ +          if(!(_.find(uniqPurchaseWindows, pw))){ +            uniqPurchaseWindows.push(pw) +          } +        }) +      }) +      return { +        type: 'purchase_windows_edit', +        modalProps: { +          vehicleJourneys: vehicleJourneys, +          purchase_windows: uniqPurchaseWindows +        }, +        confirmModal: {} +      }      case 'SELECT_CP_EDIT_MODAL':        newModalProps = _.assign({}, state.modalProps, {selectedCompany : action.selectedItem})        return _.assign({}, state, {modalProps: newModalProps}) @@ -65,6 +82,9 @@ export default function modal(state = {}, action) {      case 'SELECT_TT_CALENDAR_MODAL':        newModalProps = _.assign({}, state.modalProps, {selectedTimetable : action.selectedItem})        return _.assign({}, state, {modalProps: newModalProps}) +    case 'SELECT_PURCHASE_WINDOW_MODAL': +      newModalProps = _.assign({}, state.modalProps, {selectedPurchaseWindow : action.selectedItem}) +      return _.assign({}, state, {modalProps: newModalProps})      case 'ADD_SELECTED_TIMETABLE':        if(state.modalProps.selectedTimetable){          newModalProps = JSON.parse(JSON.stringify(state.modalProps)) @@ -73,6 +93,14 @@ export default function modal(state = {}, action) {          }          return _.assign({}, state, {modalProps: newModalProps})        } +    case 'ADD_SELECTED_PURCHASE_WINDOW': +      if(state.modalProps.selectedPurchaseWindow){ +        newModalProps = JSON.parse(JSON.stringify(state.modalProps)) +        if (!_.find(newModalProps.purchase_windows, newModalProps.selectedPurchaseWindow)){ +          newModalProps.purchase_windows.push(newModalProps.selectedPurchaseWindow) +        } +        return _.assign({}, state, {modalProps: newModalProps}) +      }      case 'DELETE_CALENDAR_MODAL':        newModalProps = JSON.parse(JSON.stringify(state.modalProps))        let timetablesModal = state.modalProps.timetables.slice(0) @@ -92,6 +120,25 @@ export default function modal(state = {}, action) {        newModalProps.vehicleJourneys = vehicleJourneysModal        newModalProps.timetables = timetablesModal        return _.assign({}, state, {modalProps: newModalProps}) +    case 'DELETE_PURCHASE_WINDOW_MODAL': +        newModalProps = JSON.parse(JSON.stringify(state.modalProps)) +        let purchase_windows = state.modalProps.purchase_windows.slice(0) +        purchase_windows.map((tt, i) =>{ +          if(tt == action.purchaseWindow){ +            purchase_windows.splice(i, 1) +          } +        }) +        vehicleJourneysModal = state.modalProps.vehicleJourneys.slice(0) +        vehicleJourneysModal.map((vj) =>{ +          vj.purchase_windows.map((tt, i) =>{ +            if (_.isEqual(tt, action.purchaseWindow)){ +              vj.purchase_windows.splice(i, 1) +            } +          }) +        }) +        newModalProps.vehicleJourneys = vehicleJourneysModal +        newModalProps.purchase_windows = purchase_windows +        return _.assign({}, state, {modalProps: newModalProps})      case 'CREATE_VEHICLEJOURNEY_MODAL':        let selectedJP = {}        if (window.jpOrigin){ @@ -135,4 +182,4 @@ export default function modal(state = {}, action) {      default:        return state    } -}
\ No newline at end of file +} diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 7fed867fa..15d6abe38 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -155,6 +155,21 @@ export default function vehicleJourneys(state = [], action) {            return vj          }        }) +      case 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS': +        let newWindows = JSON.parse(JSON.stringify(action.purchase_windows)) +        return state.map((vj,i) =>{ +          if(vj.selected){ +            let updatedVJ = _.assign({}, vj) +            action.vehicleJourneys.map((vjm, j) =>{ +              if(vj.objectid == vjm.objectid){ +                updatedVJ.purchase_windows = newWindows +              } +            }) +            return updatedVJ +          }else{ +            return vj +          } +        })      case 'SHIFT_VEHICLEJOURNEY':        return state.map((vj, i) => {          if (vj.selected){ diff --git a/app/models/calendar/period.rb b/app/models/calendar/period.rb index 1c423dfcc..56ab722fe 100644 --- a/app/models/calendar/period.rb +++ b/app/models/calendar/period.rb @@ -1,5 +1,5 @@  class Calendar < ActiveRecord::Base -   +    class Period      include ActiveAttr::Model diff --git a/app/models/chouette/purchase_window.rb b/app/models/chouette/purchase_window.rb index 9f68d4408..e89a0ec7f 100644 --- a/app/models/chouette/purchase_window.rb +++ b/app/models/chouette/purchase_window.rb @@ -11,11 +11,12 @@ module Chouette      has_paper_trail      belongs_to :referential +    has_and_belongs_to_many :vehicle_journeys, :class_name => 'Chouette::VehicleJourney'      validates_presence_of :name, :referential      scope :contains_date, ->(date) { where('date ? <@ any (date_ranges)', date) } - +          def self.ransackable_scopes(auth_object = nil)        [:contains_date]      end diff --git a/app/models/chouette/vehicle_journey.rb b/app/models/chouette/vehicle_journey.rb index 67bbbe927..983bf5363 100644 --- a/app/models/chouette/vehicle_journey.rb +++ b/app/models/chouette/vehicle_journey.rb @@ -1,3 +1,4 @@ +# coding: utf-8  module Chouette    class VehicleJourney < Chouette::TridentActiveRecord      has_paper_trail @@ -23,6 +24,7 @@ module Chouette      belongs_to :journey_pattern      has_and_belongs_to_many :footnotes, :class_name => 'Chouette::Footnote' +    has_and_belongs_to_many :purchase_windows, :class_name => 'Chouette::PurchaseWindow'      validates_presence_of :route      validates_presence_of :journey_pattern @@ -139,7 +141,7 @@ module Chouette      end      def update_has_and_belongs_to_many_from_state item -      ['time_tables', 'footnotes'].each do |assos| +      ['time_tables', 'footnotes', 'purchase_windows'].each do |assos|          saved = self.send(assos).map(&:id)          (saved - item[assos].map{|t| t['id']}).each do |id| diff --git a/app/models/concerns/period_support.rb b/app/models/concerns/period_support.rb index f512c4e89..e17451fe4 100644 --- a/app/models/concerns/period_support.rb +++ b/app/models/concerns/period_support.rb @@ -7,7 +7,7 @@ module PeriodSupport      def init_date_ranges        self.date_ranges ||= []      end -     +      ### Calendar::Period      # Required by coocon      def build_period @@ -77,4 +77,4 @@ module PeriodSupport      private :clear_periods    end -end
\ No newline at end of file +end diff --git a/app/views/autocomplete_purchase_windows/index.rabl b/app/views/autocomplete_purchase_windows/index.rabl new file mode 100644 index 000000000..1d0287602 --- /dev/null +++ b/app/views/autocomplete_purchase_windows/index.rabl @@ -0,0 +1,12 @@ +collection @purchase_windows, :object_root => false + +node do |window| +  { +    :id => window.id, +    :name => window.name, +    :objectid => window.objectid, +    :color => window.color, +    :short_id => window.get_objectid.short_id, +    :text => "<strong><span class='fa fa-circle' style='color:" + (window.color ? window.color : '#4b4b4b') + "'></span> " + window.name + " - " + window.get_objectid.short_id + "</strong>" +  } +end diff --git a/app/views/journey_patterns_collections/show.html.slim b/app/views/journey_patterns_collections/show.html.slim index 834501da3..97a62f7db 100644 --- a/app/views/journey_patterns_collections/show.html.slim +++ b/app/views/journey_patterns_collections/show.html.slim @@ -18,5 +18,6 @@    | window.journeyPatternLength = #{@journey_patterns.total_entries()};    | window.journeyPatternsPerPage = #{@ppage};    | window.perms = #{raw @perms} +  | window.features = #{raw @features};  = javascript_pack_tag 'journey_patterns/index.js' diff --git a/app/views/vehicle_journeys/index.html.slim b/app/views/vehicle_journeys/index.html.slim index 4ad9d524d..595646808 100644 --- a/app/views/vehicle_journeys/index.html.slim +++ b/app/views/vehicle_journeys/index.html.slim @@ -25,6 +25,7 @@    | window.vehicleJourneysPerPage = #{@ppage};    | window.line_footnotes = #{raw @footnotes};    | window.perms = #{raw @perms}; +  | window.features = #{raw @features};    | window.I18n = #{(I18n.backend.send(:translations).to_json).html_safe};  = javascript_pack_tag 'vehicle_journeys/index.js' diff --git a/app/views/vehicle_journeys/show.rabl b/app/views/vehicle_journeys/show.rabl index a39a53b05..b35ae65f9 100644 --- a/app/views/vehicle_journeys/show.rabl +++ b/app/views/vehicle_journeys/show.rabl @@ -28,6 +28,12 @@ child(:time_tables, :object_root => false) do |time_tables|    end  end +if has_feature? :purchase_windows +  child(:purchase_windows, :object_root => false) do |purchase_windows| +    attributes :id, :objectid, :name, :color +  end +end +  child :footnotes, :object_root => false do |footnotes|    attributes :id, :code, :label  end diff --git a/config/routes.rb b/config/routes.rb index e05f5d365..22ff58724 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,6 +112,7 @@ ChouetteIhm::Application.routes.draw do      resources :autocomplete_stop_areas, only: [:show, :index] do        get 'around', on: :member      end +    resources :autocomplete_purchase_windows, only: [:index]       get :select_compliance_control_set      post :validate, on: :member      resources :autocomplete_time_tables, only: [:index] diff --git a/db/migrate/20171227113809_create_join_table_purchase_windows_vehicle_journeys.rb b/db/migrate/20171227113809_create_join_table_purchase_windows_vehicle_journeys.rb new file mode 100644 index 000000000..5460878aa --- /dev/null +++ b/db/migrate/20171227113809_create_join_table_purchase_windows_vehicle_journeys.rb @@ -0,0 +1,8 @@ +class CreateJoinTablePurchaseWindowsVehicleJourneys < ActiveRecord::Migration +  def change +    create_join_table :purchase_windows, :vehicle_journeys do |t| +      t.integer :purchase_window_id, limit: 8 +      t.integer :vehicle_journey_id, limit: 8 +    end +  end +end diff --git a/db/schema.rb b/db/schema.rb index 182df3159..67c42f568 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,12 +11,12 @@  #  # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171220164059) do +ActiveRecord::Schema.define(version: 20171227113809) do    # These are extensions that must be enabled in order to support this database    enable_extension "plpgsql" -  enable_extension "postgis"    enable_extension "hstore" +  enable_extension "postgis"    enable_extension "unaccent"    create_table "access_links", id: :bigserial, force: :cascade do |t| @@ -403,9 +403,9 @@ ActiveRecord::Schema.define(version: 20171220164059) 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 @@ -586,6 +586,11 @@ ActiveRecord::Schema.define(version: 20171220164059) do    add_index "purchase_windows", ["referential_id"], name: "index_purchase_windows_on_referential_id", using: :btree +  create_table "purchase_windows_vehicle_journeys", id: false, force: :cascade do |t| +    t.integer "purchase_window_id", limit: 8 +    t.integer "vehicle_journey_id", limit: 8 +  end +    create_table "referential_clonings", id: :bigserial, force: :cascade do |t|      t.string   "status"      t.datetime "started_at" diff --git a/spec/controllers/autocomplete_purchase_windows_controller_spec.rb b/spec/controllers/autocomplete_purchase_windows_controller_spec.rb new file mode 100644 index 000000000..cea600ea8 --- /dev/null +++ b/spec/controllers/autocomplete_purchase_windows_controller_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +RSpec.describe AutocompletePurchaseWindowsController, type: :controller do +  login_user + +  let(:referential) { Referential.first } +  let!(:window) { create :purchase_window, referential: referential, name: 'écolà militaire' } + +  describe 'GET #index' do +    it 'should be unauthorized' do +      expect { get(:index, referential_id: referential.id) }.to raise_error(FeatureChecker::NotAuthorizedError) +    end + +    with_feature "purchase_windows" do +      let(:request){ get(:index, referential_id: referential.id) } +      before do +        request +      end + +      it 'should be successful' do +        expect(response).to be_success +      end + +      context 'search by name' do +        let(:request){ get :index, referential_id: referential.id, q: {name_or_objectid_cont_any: 'écolà'}, :format => :json } +        it 'should be successful' do +          expect(response).to be_success +          expect(assigns(:purchase_windows)).to eq([window]) +        end +      end +    end +  end +end diff --git a/spec/controllers/vehicle_journeys_controller_spec.rb b/spec/controllers/vehicle_journeys_controller_spec.rb index c9356fffa..416450c21 100644 --- a/spec/controllers/vehicle_journeys_controller_spec.rb +++ b/spec/controllers/vehicle_journeys_controller_spec.rb @@ -10,6 +10,7 @@ RSpec.describe VehicleJourneysController, :type => :controller do      before do        allow(controller).to receive(:pundit_user).and_return(user_context) +      allow(controller).to receive(:current_organisation).and_return(@user.organisation)      end      it 'computes them correctly if not authorized' do diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 585636124..a7fe0a162 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -44,6 +44,6 @@ Diff: #{diff}    def diff      RSpec::Support::Differ.new(        color: RSpec::Matchers.configuration.color? -    ).diff_as_string(@expected, @original) +    ).diff_as_string(@original, @expected)    end  end diff --git a/spec/javascript/vehicle_journeys/actions_spec.js b/spec/javascript/vehicle_journeys/actions_spec.js index 789507482..3af19ebc3 100644 --- a/spec/javascript/vehicle_journeys/actions_spec.js +++ b/spec/javascript/vehicle_journeys/actions_spec.js @@ -209,6 +209,13 @@ describe('when clicking on edit notes modal', () => {      expect(actions.openNotesEditModal(vehicleJourney)).toEqual(expectedAction)    })  }) + + //  ___ ___   ___ _____ _  _  ___ _____ ___ ___ + // | __/ _ \ / _ \_   _| \| |/ _ \_   _| __/ __| + // | _| (_) | (_) || | | .` | (_) || | | _|\__ \ + // |_| \___/ \___/ |_| |_|\_|\___/ |_| |___|___/ + // +  describe('when clicking on a footnote button inside footnote modal', () => {    it('should create an action to toggle this footnote', () => {      const footnote = {}, isShown = true @@ -230,6 +237,13 @@ describe('when clicking on validate button inside footnote modal', () => {      expect(actions.editVehicleJourneyNotes(footnotes)).toEqual(expectedAction)    })  }) + + //  _____ ___ __  __ ___ _____ _   ___ _    ___ ___ + // |_   _|_ _|  \/  | __|_   _/_\ | _ ) |  | __/ __| + //   | |  | || |\/| | _|  | |/ _ \| _ \ |__| _|\__ \ + //   |_| |___|_|  |_|___| |_/_/ \_\___/____|___|___/ + // +  describe('when clicking on calendar button in toolbox', () => {    it('should create an action to open calendar modal', () => {      const vehicleJourneys = [] @@ -288,6 +302,83 @@ describe('when using select2 to pick a timetable', () => {      expect(actions.selectTTCalendarsModal(selectedTT)).toEqual(expectedAction)    })  }) + + //  ___ _   _ ___  ___ _  _   _   ___ ___ + // | _ \ | | | _ \/ __| || | /_\ / __| __| + // |  _/ |_| |   / (__| __ |/ _ \\__ \ _| + // |_|  \___/|_|_\\___|_||_/_/_\_\___/___|__ + // \ \    / /_ _| \| |   \ / _ \ \    / / __| + //  \ \/\/ / | || .` | |) | (_) \ \/\/ /\__ \ + //   \_/\_/ |___|_|\_|___/ \___/ \_/\_/ |___/ + // + +describe('when clicking on purchase window button in toolbox', () => { +  it('should create an action to open purchase window modal', () => { +    const vehicleJourneys = [] +    const expectedAction = { +      type: 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL', +      vehicleJourneys +    } +    expect(actions.openPurchaseWindowsEditModal(vehicleJourneys)).toEqual(expectedAction) +  }) +}) +describe('when clicking on delete button next to a purchase window inside modal', () => { +  it('should create an action to delete purchase window from selected vehicle journeys', () => { +    const purchaseWindow = {} +    const expectedAction = { +      type: 'DELETE_PURCHASE_WINDOW_MODAL', +      purchaseWindow +    } +    expect(actions.deletePurchaseWindowsModal(purchaseWindow)).toEqual(expectedAction) +  }) +}) +describe('when clicking on validate button inside purchase windows modal', () => { +  it('should create an action to update vj purchase windows', () => { +    const vehicleJourneys = [] +    const purchase_windows = [] +    const expectedAction = { +      type: 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS', +      vehicleJourneys, +      purchase_windows +    } +    expect(actions.editVehicleJourneyPurchaseWindows(vehicleJourneys, purchase_windows)).toEqual(expectedAction) +  }) +}) +describe('when clicking on add button inside purchase windows modal', () => { +  it('should create an action to add the selected purchase window to preselected vjs', () => { +    const expectedAction = { +      type: 'ADD_SELECTED_PURCHASE_WINDOW', +    } +    expect(actions.addSelectedPurchaseWindow()).toEqual(expectedAction) +  }) +}) +describe('when using select2 to pick a purchase window', () => { +  it('should create an action to select a purchase window inside modal', () => { +    let selectedTT = { +      id: 1, +      objectid: 2, +      name: 'test', +      color: 'color', +    } +    const expectedAction = { +      type: 'SELECT_PURCHASE_WINDOW_MODAL', +      selectedItem:{ +        id: selectedTT.id, +        objectid: selectedTT.objectid, +        name: selectedTT.name, +        color: "color" +      } +    } +    expect(actions.selectPurchaseWindowsModal(selectedTT)).toEqual(expectedAction) +  }) +}) + + //  ___ ___ _  _____ ___ ___  ___ + // | __|_ _| ||_   _| __| _ \/ __| + // | _| | || |__| | | _||   /\__ \ + // |_| |___|____|_| |___|_|_\|___/ + // +  describe('when clicking on reset button inside query filters', () => {    it('should create an action to reset the query filters', () => {      const expectedAction = { diff --git a/spec/javascript/vehicle_journeys/reducers/modal_spec.js b/spec/javascript/vehicle_journeys/reducers/modal_spec.js index 69de9168b..ea8a002d2 100644 --- a/spec/javascript/vehicle_journeys/reducers/modal_spec.js +++ b/spec/javascript/vehicle_journeys/reducers/modal_spec.js @@ -91,6 +91,12 @@ describe('modal reducer', () => {      ).toEqual(newState)    }) +   //  _____ ___ __  __ ___ _____ _   ___ _    ___ ___ +   // |_   _|_ _|  \/  | __|_   _/_\ | _ ) |  | __/ __| +   //   | |  | || |\/| | _|  | |/ _ \| _ \ |__| _|\__ \ +   //   |_| |___|_|  |_|___| |_/_/ \_\___/____|___|___/ +   // +    it('should handle EDIT_CALENDARS_VEHICLEJOURNEY_MODAL', () => {      let vehicleJourneys = []      let modalPropsResult = { @@ -158,6 +164,82 @@ describe('modal reducer', () => {      ).toEqual(newState)    }) +  //  ___ _   _ ___  ___ _  _   _   ___ ___ +  // | _ \ | | | _ \/ __| || | /_\ / __| __| +  // |  _/ |_| |   / (__| __ |/ _ \\__ \ _| +  // |_|  \___/|_|_\\___|_||_/_/_\_\___/___|__ +  // \ \    / /_ _| \| |   \ / _ \ \    / / __| +  //  \ \/\/ / | || .` | |) | (_) \ \/\/ /\__ \ +  //   \_/\_/ |___|_|\_|___/ \___/ \_/\_/ |___/ +  // + +  it('should handle EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL', () => { +    let vehicleJourneys = [] +    let modalPropsResult = { +      vehicleJourneys: [], +      purchase_windows: [] +    } +    expect( +      modalReducer(state, { +        type: 'EDIT_PURCHASE_WINDOWS_VEHICLEJOURNEY_MODAL', +        vehicleJourneys +      }) +    ).toEqual(Object.assign({}, state, {type: 'purchase_windows_edit', modalProps: modalPropsResult})) +  }) + +  it('should handle SELECT_PURCHASE_WINDOW_MODAL', () => { +    let newModalProps = {selectedPurchaseWindow : {id: 1}} +    expect( +      modalReducer(state, { +        type: 'SELECT_PURCHASE_WINDOW_MODAL', +        selectedItem: {id: 1} +      }) +    ).toEqual(Object.assign({}, state, {modalProps: newModalProps})) +  }) + +  it('should handle ADD_SELECTED_PURCHASE_WINDOW', () => { +    let fakeWindows = [{'test': 'test'}, {'test 2': 'test 2'}] +    let newWindows = [{'test': 'test'}, {'test 2': 'test 2'}, {'add': 'add'}] +    let fakeVehicleJourneys= [{purchase_windows: fakeWindows}, {purchase_windows: newWindows}] +    state.modalProps.vehicleJourneys = fakeVehicleJourneys +    state.modalProps.purchase_windows = fakeWindows +    state.modalProps.selectedPurchaseWindow = {'add': 'add'} +    let newState = { +      type: '', +      modalProps:{ +        vehicleJourneys: fakeVehicleJourneys, +        purchase_windows: [{'test': 'test'},{'test 2': 'test 2'},{'add': 'add'}], +        selectedPurchaseWindow: {'add': 'add'} +      }, +      confirmModal: {} +    } +    expect( +      modalReducer(state, { +        type: 'ADD_SELECTED_PURCHASE_WINDOW', +      }) +    ).toEqual(newState) +  }) + +  it('should handle DELETE_PURCHASE_WINDOW_MODAL', () => { +    let deletableWindow = {'delete': 'delete'} +    let fakeWindows = [{'test': 'test'}, {'test 2': 'test 2'}, deletableWindow] +    let newWindows = [{'test': 'test'}, {'test 2': 'test 2'}] +    let fakeVehicleJourneys= [{purchase_windows: fakeWindows}, {purchase_windows: newWindows}] +    state.modalProps = Object.assign({}, state.modalProps,{vehicleJourneys : fakeVehicleJourneys, purchase_windows: fakeWindows }) +    let newState = { +      // for the sake of the test, no need to specify the type +      type: '', +      modalProps:{vehicleJourneys: [{purchase_windows: newWindows},{purchase_windows: newWindows}], purchase_windows: newWindows}, +      confirmModal: {} +    } +    expect( +      modalReducer(state, { +        type: 'DELETE_PURCHASE_WINDOW_MODAL', +        purchaseWindow: deletableWindow +      }) +    ).toEqual(newState) +  }) +    it('should handle SELECT_CP_EDIT_MODAL', () => {      let newModalProps = {selectedCompany : {name: 'ALBATRANS'}}      expect( diff --git a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js index 1c2cc1577..c834de1f6 100644 --- a/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js +++ b/spec/javascript/vehicle_journeys/reducers/vehicleJourneys_spec.js @@ -254,7 +254,6 @@ describe('vehicleJourneys reducer', () => {      ).toEqual([newVJ, state[1]])    }) -    it('should handle EDIT_VEHICLEJOURNEYS_TIMETABLES', () => {      let newState = JSON.parse(JSON.stringify(state))      newState[0].time_tables = [fakeTimeTables[0]] @@ -266,4 +265,16 @@ describe('vehicleJourneys reducer', () => {        })      ).toEqual(newState)    }) + +  it('should handle EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS', () => { +    let newState = JSON.parse(JSON.stringify(state)) +    newState[0].purchase_windows = [fakeTimeTables[0]] +    expect( +      vjReducer(state, { +        type: 'EDIT_VEHICLEJOURNEYS_PURCHASE_WINDOWS', +        vehicleJourneys: state, +        purchase_windows: [fakeTimeTables[0]] +      }) +    ).toEqual(newState) +  })  }) diff --git a/spec/models/chouette/vehicle_journey_spec.rb b/spec/models/chouette/vehicle_journey_spec.rb index ac9b21ceb..60ab2b818 100644 --- a/spec/models/chouette/vehicle_journey_spec.rb +++ b/spec/models/chouette/vehicle_journey_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper'  describe Chouette::VehicleJourney, :type => :model do    it { is_expected.to be_versioned } +  it { should have_and_belong_to_many(:purchase_windows) }    it "must be valid with an at-stop day offset of 1" do      vehicle_journey = create( | 
