mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	Fix: add option to choose 24 hour working slot (#4018)
* Add option to choose 24 hour working slot * fix spec * add check to update open hour and close hour for open all day * update 24 hour working slot change in widget side * add validation to check open_all_day and closed_all_day true at the same time
This commit is contained in:
		| @@ -463,7 +463,8 @@ | |||||||
|         "HOURS": "hours", |         "HOURS": "hours", | ||||||
|         "VALIDATION_ERROR": "Starting time should be before closing time.", |         "VALIDATION_ERROR": "Starting time should be before closing time.", | ||||||
|         "CHOOSE": "Choose" |         "CHOOSE": "Choose" | ||||||
|       } |       }, | ||||||
|  |       "ALL_DAY":"All-Day" | ||||||
|     }, |     }, | ||||||
|     "IMAP": { |     "IMAP": { | ||||||
|       "TITLE": "IMAP", |       "TITLE": "IMAP", | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
|       <input |       <input | ||||||
|         v-model="isDayEnabled" |         v-model="isDayEnabled" | ||||||
|         name="enable-day" |         name="enable-day" | ||||||
|         class="enable-day" |         class="enable-checkbox" | ||||||
|         type="checkbox" |         type="checkbox" | ||||||
|         :title="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.ENABLE')" |         :title="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.ENABLE')" | ||||||
|       /> |       /> | ||||||
| @@ -14,26 +14,38 @@ | |||||||
|     </div> |     </div> | ||||||
|     <div v-if="isDayEnabled" class="hours-select-wrap"> |     <div v-if="isDayEnabled" class="hours-select-wrap"> | ||||||
|       <div class="hours-range"> |       <div class="hours-range"> | ||||||
|  |         <div class="checkbox-wrap open-all-day"> | ||||||
|  |           <input | ||||||
|  |             v-model="isOpenAllDay" | ||||||
|  |             name="enable-open-all-day" | ||||||
|  |             class="enable-checkbox" | ||||||
|  |             type="checkbox" | ||||||
|  |             :title="$t('INBOX_MGMT.BUSINESS_HOURS.ALL_DAY')" | ||||||
|  |           /> | ||||||
|  |           <span>{{ $t('INBOX_MGMT.BUSINESS_HOURS.ALL_DAY') }}</span> | ||||||
|  |         </div> | ||||||
|         <multiselect |         <multiselect | ||||||
|           v-model="fromTime" |           v-model="fromTime" | ||||||
|           :options="timeSlots" |           :options="fromTimeSlots" | ||||||
|           deselect-label="" |           deselect-label="" | ||||||
|           select-label="" |           select-label="" | ||||||
|           selected-label="" |           selected-label="" | ||||||
|           :placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')" |           :placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')" | ||||||
|           :allow-empty="false" |           :allow-empty="false" | ||||||
|  |           :disabled="isOpenAllDay" | ||||||
|         /> |         /> | ||||||
|         <div class="separator-icon"> |         <div class="separator-icon"> | ||||||
|           <fluent-icon icon="subtract" type="solid" size="16" /> |           <fluent-icon icon="subtract" type="solid" size="16" /> | ||||||
|         </div> |         </div> | ||||||
|         <multiselect |         <multiselect | ||||||
|           v-model="toTime" |           v-model="toTime" | ||||||
|           :options="timeSlots" |           :options="toTimeSlots" | ||||||
|           deselect-label="" |           deselect-label="" | ||||||
|           select-label="" |           select-label="" | ||||||
|           selected-label="" |           selected-label="" | ||||||
|           :placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')" |           :placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')" | ||||||
|           :allow-empty="false" |           :allow-empty="false" | ||||||
|  |           :disabled="isOpenAllDay" | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|       <div v-if="hasError" class="date-error"> |       <div v-if="hasError" class="date-error"> | ||||||
| @@ -79,9 +91,14 @@ export default { | |||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     timeSlots() { |     fromTimeSlots() { | ||||||
|       return timeSlots; |       return timeSlots; | ||||||
|     }, |     }, | ||||||
|  |     toTimeSlots() { | ||||||
|  |       return timeSlots.filter(slot => { | ||||||
|  |         return slot !== '12:00 AM'; | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|     isDayEnabled: { |     isDayEnabled: { | ||||||
|       get() { |       get() { | ||||||
|         return this.timeSlot.from && this.timeSlot.to; |         return this.timeSlot.from && this.timeSlot.to; | ||||||
| @@ -93,12 +110,14 @@ export default { | |||||||
|               from: timeSlots[0], |               from: timeSlots[0], | ||||||
|               to: timeSlots[16], |               to: timeSlots[16], | ||||||
|               valid: true, |               valid: true, | ||||||
|  |               openAllDay: false, | ||||||
|             } |             } | ||||||
|           : { |           : { | ||||||
|               ...this.timeSlot, |               ...this.timeSlot, | ||||||
|               from: '', |               from: '', | ||||||
|               to: '', |               to: '', | ||||||
|               valid: false, |               valid: false, | ||||||
|  |               openAllDay: false, | ||||||
|             }; |             }; | ||||||
|         this.$emit('update', newSlot); |         this.$emit('update', newSlot); | ||||||
|       }, |       }, | ||||||
| @@ -146,15 +165,39 @@ export default { | |||||||
|       return parse(this.toTime, 'hh:mm a', new Date()); |       return parse(this.toTime, 'hh:mm a', new Date()); | ||||||
|     }, |     }, | ||||||
|     totalHours() { |     totalHours() { | ||||||
|       const totalHours = differenceInMinutes(this.toDate, this.fromDate) / 60; |       if (this.timeSlot.openAllDay) { | ||||||
|       if (this.toTime === '12:00 AM') { |         return 24; | ||||||
|         return 24 + totalHours; |  | ||||||
|       } |       } | ||||||
|  |       const totalHours = differenceInMinutes(this.toDate, this.fromDate) / 60; | ||||||
|       return totalHours; |       return totalHours; | ||||||
|     }, |     }, | ||||||
|     hasError() { |     hasError() { | ||||||
|       return !this.timeSlot.valid; |       return !this.timeSlot.valid; | ||||||
|     }, |     }, | ||||||
|  |     isOpenAllDay: { | ||||||
|  |       get() { | ||||||
|  |         return this.timeSlot.openAllDay; | ||||||
|  |       }, | ||||||
|  |       set(value) { | ||||||
|  |         if (value) { | ||||||
|  |           this.$emit('update', { | ||||||
|  |             ...this.timeSlot, | ||||||
|  |             from: '12:00 AM', | ||||||
|  |             to: '11:59 PM', | ||||||
|  |             valid: true, | ||||||
|  |             openAllDay: value, | ||||||
|  |           }); | ||||||
|  |         } else { | ||||||
|  |           this.$emit('update', { | ||||||
|  |             ...this.timeSlot, | ||||||
|  |             from: '09:00 AM', | ||||||
|  |             to: '05:00 PM', | ||||||
|  |             valid: true, | ||||||
|  |             openAllDay: value, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
| @@ -182,7 +225,7 @@ export default { | |||||||
|   box-sizing: content-box; |   box-sizing: content-box; | ||||||
|   border-bottom: 1px solid var(--color-border-light); |   border-bottom: 1px solid var(--color-border-light); | ||||||
| } | } | ||||||
| .enable-day { | .enable-checkbox { | ||||||
|   margin: 0; |   margin: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -240,4 +283,17 @@ export default { | |||||||
|   font-size: var(--font-size-mini); |   font-size: var(--font-size-mini); | ||||||
|   color: var(--r-300); |   color: var(--r-300); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .open-all-day { | ||||||
|  |   margin-right: var(--space-medium); | ||||||
|  |   span { | ||||||
|  |     font-size: var(--font-size-small); | ||||||
|  |     font-weight: var(--font-weight-medium); | ||||||
|  |     margin-left: var(--space-smaller); | ||||||
|  |   } | ||||||
|  |   input { | ||||||
|  |     font-size: var(--font-size-small); | ||||||
|  |     font-weight: var(--font-weight-medium); | ||||||
|  |   } | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -86,6 +86,7 @@ export const timeSlotParse = timeSlots => { | |||||||
|       close_hour: closeHour, |       close_hour: closeHour, | ||||||
|       close_minutes: closeMinutes, |       close_minutes: closeMinutes, | ||||||
|       closed_all_day: closedAllDay, |       closed_all_day: closedAllDay, | ||||||
|  |       open_all_day: openAllDay, | ||||||
|     } = slot; |     } = slot; | ||||||
|     const from = closedAllDay ? '' : getTime(openHour, openMinutes); |     const from = closedAllDay ? '' : getTime(openHour, openMinutes); | ||||||
|     const to = closedAllDay ? '' : getTime(closeHour, closeMinutes); |     const to = closedAllDay ? '' : getTime(closeHour, closeMinutes); | ||||||
| @@ -95,13 +96,15 @@ export const timeSlotParse = timeSlots => { | |||||||
|       to, |       to, | ||||||
|       from, |       from, | ||||||
|       valid: !closedAllDay, |       valid: !closedAllDay, | ||||||
|  |       openAllDay, | ||||||
|     }; |     }; | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const timeSlotTransform = timeSlots => { | export const timeSlotTransform = timeSlots => { | ||||||
|   return timeSlots.map(slot => { |   return timeSlots.map(slot => { | ||||||
|     const closed = !(slot.to && slot.from); |     const closed = slot.openAllDay ? false : !(slot.to && slot.from); | ||||||
|  |     const openAllDay = slot.openAllDay; | ||||||
|     let fromDate = ''; |     let fromDate = ''; | ||||||
|     let toDate = ''; |     let toDate = ''; | ||||||
|     let openHour = ''; |     let openHour = ''; | ||||||
| @@ -125,6 +128,7 @@ export const timeSlotTransform = timeSlots => { | |||||||
|       open_minutes: openMinutes, |       open_minutes: openMinutes, | ||||||
|       close_hour: closeHour, |       close_hour: closeHour, | ||||||
|       close_minutes: closeMinutes, |       close_minutes: closeMinutes, | ||||||
|  |       open_all_day: openAllDay, | ||||||
|     }; |     }; | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ describe('#timeSlotParse', () => { | |||||||
|       close_hour: 4, |       close_hour: 4, | ||||||
|       close_minutes: 30, |       close_minutes: 30, | ||||||
|       closed_all_day: false, |       closed_all_day: false, | ||||||
|  |       open_all_day: false, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     expect(timeSlotParse([slot])).toStrictEqual([ |     expect(timeSlotParse([slot])).toStrictEqual([ | ||||||
| @@ -48,6 +49,7 @@ describe('#timeSlotParse', () => { | |||||||
|         from: '01:30 AM', |         from: '01:30 AM', | ||||||
|         to: '04:30 AM', |         to: '04:30 AM', | ||||||
|         valid: true, |         valid: true, | ||||||
|  |         openAllDay: false, | ||||||
|       }, |       }, | ||||||
|     ]); |     ]); | ||||||
|   }); |   }); | ||||||
| @@ -60,6 +62,7 @@ describe('#timeSlotTransform', () => { | |||||||
|       from: '01:30 AM', |       from: '01:30 AM', | ||||||
|       to: '04:30 AM', |       to: '04:30 AM', | ||||||
|       valid: true, |       valid: true, | ||||||
|  |       openAllDay: false, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     expect(timeSlotTransform([slot])).toStrictEqual([ |     expect(timeSlotTransform([slot])).toStrictEqual([ | ||||||
| @@ -70,6 +73,7 @@ describe('#timeSlotTransform', () => { | |||||||
|         close_hour: 4, |         close_hour: 4, | ||||||
|         close_minutes: 30, |         close_minutes: 30, | ||||||
|         closed_all_day: false, |         closed_all_day: false, | ||||||
|  |         open_all_day: false, | ||||||
|       }, |       }, | ||||||
|     ]); |     ]); | ||||||
|   }); |   }); | ||||||
|   | |||||||
| @@ -31,9 +31,12 @@ export default { | |||||||
|         closeHour, |         closeHour, | ||||||
|         closeMinute, |         closeMinute, | ||||||
|         closedAllDay, |         closedAllDay, | ||||||
|  |         openAllDay, | ||||||
|       } = this.currentDayAvailability; |       } = this.currentDayAvailability; | ||||||
|       const { utcOffset } = this.channelConfig; |       const { utcOffset } = this.channelConfig; | ||||||
|  |  | ||||||
|  |       if (openAllDay) return true; | ||||||
|  |  | ||||||
|       if (closedAllDay) return false; |       if (closedAllDay) return false; | ||||||
|  |  | ||||||
|       const startTime = buildDateFromTime(openHour, openMinute, utcOffset); |       const startTime = buildDateFromTime(openHour, openMinute, utcOffset); | ||||||
| @@ -56,6 +59,7 @@ export default { | |||||||
|         openMinute: workingHourConfig.open_minutes, |         openMinute: workingHourConfig.open_minutes, | ||||||
|         closeHour: workingHourConfig.close_hour, |         closeHour: workingHourConfig.close_hour, | ||||||
|         closeMinute: workingHourConfig.close_minutes, |         closeMinute: workingHourConfig.close_minutes, | ||||||
|  |         openAllDay: workingHourConfig.open_all_day, | ||||||
|       }; |       }; | ||||||
|     }, |     }, | ||||||
|     isInBusinessHours() { |     isInBusinessHours() { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| module OutOfOffisable | module OutOfOffisable | ||||||
|   extend ActiveSupport::Concern |   extend ActiveSupport::Concern | ||||||
|  |  | ||||||
|   OFFISABLE_ATTRS = %w[day_of_week closed_all_day open_hour open_minutes close_hour close_minutes].freeze |   OFFISABLE_ATTRS = %w[day_of_week closed_all_day open_hour open_minutes close_hour close_minutes open_all_day].freeze | ||||||
|  |  | ||||||
|   included do |   included do | ||||||
|     has_many :working_hours, dependent: :destroy_async |     has_many :working_hours, dependent: :destroy_async | ||||||
| @@ -29,7 +29,8 @@ module OutOfOffisable | |||||||
|   #      "open_hour"=>9, |   #      "open_hour"=>9, | ||||||
|   #      "open_minutes"=>0, |   #      "open_minutes"=>0, | ||||||
|   #      "close_hour"=>17, |   #      "close_hour"=>17, | ||||||
|   #      "close_minutes"=>0},...] |   #      "close_minutes"=>0, | ||||||
|  |   #      "open_all_day=>false" },...] | ||||||
|   def update_working_hours(params) |   def update_working_hours(params) | ||||||
|     ActiveRecord::Base.transaction do |     ActiveRecord::Base.transaction do | ||||||
|       params.each do |working_hour| |       params.each do |working_hour| | ||||||
| @@ -41,12 +42,12 @@ module OutOfOffisable | |||||||
|   private |   private | ||||||
|  |  | ||||||
|   def create_default_working_hours |   def create_default_working_hours | ||||||
|     working_hours.create!(day_of_week: 0, closed_all_day: true) |     working_hours.create!(day_of_week: 0, closed_all_day: true, open_all_day: false) | ||||||
|     working_hours.create!(day_of_week: 1, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) |     working_hours.create!(day_of_week: 1, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false) | ||||||
|     working_hours.create!(day_of_week: 2, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) |     working_hours.create!(day_of_week: 2, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false) | ||||||
|     working_hours.create!(day_of_week: 3, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) |     working_hours.create!(day_of_week: 3, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false) | ||||||
|     working_hours.create!(day_of_week: 4, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) |     working_hours.create!(day_of_week: 4, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false) | ||||||
|     working_hours.create!(day_of_week: 5, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0) |     working_hours.create!(day_of_week: 5, open_hour: 9, open_minutes: 0, close_hour: 17, close_minutes: 0, open_all_day: false) | ||||||
|     working_hours.create!(day_of_week: 6, closed_all_day: true) |     working_hours.create!(day_of_week: 6, closed_all_day: true, open_all_day: false) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| #  close_minutes  :integer | #  close_minutes  :integer | ||||||
| #  closed_all_day :boolean          default(FALSE) | #  closed_all_day :boolean          default(FALSE) | ||||||
| #  day_of_week    :integer          not null | #  day_of_week    :integer          not null | ||||||
|  | #  open_all_day   :boolean          default(FALSE) | ||||||
| #  open_hour      :integer | #  open_hour      :integer | ||||||
| #  open_minutes   :integer | #  open_minutes   :integer | ||||||
| #  created_at     :datetime         not null | #  created_at     :datetime         not null | ||||||
| @@ -22,6 +23,7 @@ | |||||||
| class WorkingHour < ApplicationRecord | class WorkingHour < ApplicationRecord | ||||||
|   belongs_to :inbox |   belongs_to :inbox | ||||||
|  |  | ||||||
|  |   before_validation :ensure_open_all_day_hours | ||||||
|   before_save :assign_account |   before_save :assign_account | ||||||
|  |  | ||||||
|   validates :open_hour,     presence: true, unless: :closed_all_day? |   validates :open_hour,     presence: true, unless: :closed_all_day? | ||||||
| @@ -35,6 +37,7 @@ class WorkingHour < ApplicationRecord | |||||||
|   validates :close_minutes, inclusion: 0..59, unless: :closed_all_day? |   validates :close_minutes, inclusion: 0..59, unless: :closed_all_day? | ||||||
|  |  | ||||||
|   validate :close_after_open, unless: :closed_all_day? |   validate :close_after_open, unless: :closed_all_day? | ||||||
|  |   validate :open_all_day_and_closed_all_day | ||||||
|  |  | ||||||
|   def self.today |   def self.today | ||||||
|     find_by(day_of_week: Date.current.wday) |     find_by(day_of_week: Date.current.wday) | ||||||
| @@ -69,4 +72,19 @@ class WorkingHour < ApplicationRecord | |||||||
|  |  | ||||||
|     errors.add(:close_hour, 'Closing time cannot be before opening time') |     errors.add(:close_hour, 'Closing time cannot be before opening time') | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def ensure_open_all_day_hours | ||||||
|  |     return unless open_all_day? | ||||||
|  |  | ||||||
|  |     self.open_hour = 0 | ||||||
|  |     self.open_minutes = 0 | ||||||
|  |     self.close_hour = 23 | ||||||
|  |     self.close_minutes = 59 | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def open_all_day_and_closed_all_day | ||||||
|  |     return unless open_all_day? && closed_all_day? | ||||||
|  |  | ||||||
|  |     errors.add(:base, 'open_all_day and closed_all_day cannot be true at the same time') | ||||||
|  |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | class AddOpenAllDayToWorkingHour < ActiveRecord::Migration[6.1] | ||||||
|  |   def change | ||||||
|  |     add_column :working_hours, :open_all_day, :boolean, default: false | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -773,6 +773,7 @@ ActiveRecord::Schema.define(version: 2022_02_18_120357) do | |||||||
|     t.integer "close_minutes" |     t.integer "close_minutes" | ||||||
|     t.datetime "created_at", precision: 6, null: false |     t.datetime "created_at", precision: 6, null: false | ||||||
|     t.datetime "updated_at", precision: 6, null: false |     t.datetime "updated_at", precision: 6, null: false | ||||||
|  |     t.boolean "open_all_day", default: false | ||||||
|     t.index ["account_id"], name: "index_working_hours_on_account_id" |     t.index ["account_id"], name: "index_working_hours_on_account_id" | ||||||
|     t.index ["inbox_id"], name: "index_working_hours_on_inbox_id" |     t.index ["inbox_id"], name: "index_working_hours_on_inbox_id" | ||||||
|   end |   end | ||||||
|   | |||||||
| @@ -50,4 +50,42 @@ RSpec.describe WorkingHour do | |||||||
|       expect(described_class.today.closed_now?).to be true |       expect(described_class.today.closed_now?).to be true | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   context 'when open_all_day is true' do | ||||||
|  |     let(:inbox) { create(:inbox) } | ||||||
|  |  | ||||||
|  |     before do | ||||||
|  |       Time.zone = 'UTC' | ||||||
|  |       inbox.working_hours.find_by(day_of_week: 5).update(open_all_day: true) | ||||||
|  |       travel_to '18.02.2022 11:00'.to_datetime | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     it 'updates open hour and close hour' do | ||||||
|  |       expect(described_class.today.open_all_day?).to be true | ||||||
|  |       expect(described_class.today.open_hour).to be 0 | ||||||
|  |       expect(described_class.today.open_minutes).to be 0 | ||||||
|  |       expect(described_class.today.close_hour).to be 23 | ||||||
|  |       expect(described_class.today.close_minutes).to be 59 | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   context 'when open_all_day and closed_all_day true at the same time' do | ||||||
|  |     let(:inbox) { create(:inbox) } | ||||||
|  |  | ||||||
|  |     before do | ||||||
|  |       Time.zone = 'UTC' | ||||||
|  |       inbox.working_hours.find_by(day_of_week: 5).update(open_all_day: true) | ||||||
|  |       travel_to '18.02.2022 11:00'.to_datetime | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     it 'throws validation error' do | ||||||
|  |       working_hour = inbox.working_hours.find_by(day_of_week: 5) | ||||||
|  |       working_hour.closed_all_day = true | ||||||
|  |       expect(working_hour.invalid?).to be true | ||||||
|  |       expect do | ||||||
|  |         working_hour.save! | ||||||
|  |       end.to raise_error(ActiveRecord::RecordInvalid, | ||||||
|  |                          'Validation failed: open_all_day and closed_all_day cannot be true at the same time') | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Aswin Dev P.S
					Aswin Dev P.S