diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 949276cd..83222004 100755 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base def lookup_domain if request.subdomain.present? && request.subdomain != "www" - @license = current_license(ENV["SX_PROVISION_URL"], request.subdomain.downcase) + @license = cache_license(ENV["SX_PROVISION_URL"], request.subdomain.downcase) # request.subdomain.downcase if (!@license.nil?) # logger.info "Location - " + @license.name ActiveRecord::Base.establish_connection(website_connection(@license)) @@ -35,18 +35,31 @@ class ApplicationController < ActionController::Base # redirect_to root_url(:host => request.domain) + "store_error" render :json => [{ status: false, message: 'Invalid Access!'}] end + else + # check for license file + if check_license + current_license(ENV["SX_PROVISION_URL"]) + end end end - def current_license(url, key) - @license = License.new(url, key) + def current_license(url) + @license = License.new(url) - ##creating md5 hash - md5_hostname = Digest::MD5.new - md5key = md5_hostname.update(request.host) - if (@license.detail_with_local_cache(key, md5key.to_s) == true) - #if (@license.detail == true) + if (@license.detail_with_local_file() == true) + puts "RUN SAY BYAR" + else + return nil + end + end + def cache_license(url, lookup) + @license = License.new(url, lookup) + # Export for Key + aes = MyAesCrypt.new + aes_key, aes_iv = aes.export_key(lookup) + + if (@license.detail_with_local_cache(lookup, aes_key, aes_iv) == true) return @license else return nil @@ -93,8 +106,10 @@ class ApplicationController < ActionController::Base end private - def check_installation - if current_company.nil? + def check_license + if License.check_license_file + return true + else redirect_to install_path end end diff --git a/app/controllers/install_controller.rb b/app/controllers/install_controller.rb index 1ffc0b2d..cf16adfe 100755 --- a/app/controllers/install_controller.rb +++ b/app/controllers/install_controller.rb @@ -1,8 +1,42 @@ class InstallController < BaseController + def index + end - def index + def create + restaurant = params[:restaurant_name] + license_key = params[:license_key] + admin_user = params[:admin_user] + admin_password = params[:admin_password] + end + + def lookup_domain + if request.subdomain.present? && request.subdomain != "www" + @license = current_license(ENV["SX_PROVISION_URL"], request.subdomain.downcase) + if (!@license.nil?) + # logger.info "Location - " + @license.name + ActiveRecord::Base.establish_connection(website_connection(@license)) + # logger.info "Connecting to - " + @license.subdomain + " - "+ @license.dbhost + "@" + @license.dbschema + else + # reconnect_default_db + logger.info 'License is nil' + # redirect_to root_url(:host => request.domain) + "store_error" + render :json => [{ status: false, message: 'Invalid Access!'}] + end + end end - def create + def current_license(url, key) + @license = License.new(url, key) + + ##creating md5 hash + md5_hostname = Digest::MD5.new + md5key = md5_hostname.update(request.host) + if (@license.detail_with_local_cache(key, md5key.to_s) == true) + #if (@license.detail == true) + + return @license + else + return nil + end end end diff --git a/app/controllers/settings/dining_charges_controller.rb b/app/controllers/settings/dining_charges_controller.rb index 576703ed..72a59ce3 100755 --- a/app/controllers/settings/dining_charges_controller.rb +++ b/app/controllers/settings/dining_charges_controller.rb @@ -16,9 +16,9 @@ class Settings::DiningChargesController < ApplicationController # GET /dining_charges/new def new @dining_charge = DiningCharge.new - @dining_charge.minimum_free_time="00:30" - @dining_charge.charge_block="02:00" - @dining_charge.time_rounding_block="00:15" + @dining_charge.minimum_free_time = "00:15:00".to_datetime + @dining_charge.charge_block= "01:00:00".to_datetime + @dining_charge.time_rounding_block="00:15:00".to_datetime end # GET /dining_charges/1/edit @@ -27,15 +27,19 @@ class Settings::DiningChargesController < ApplicationController # POST /dining_charges # POST /dining_charges.json - def create + def create @dining_charge = DiningCharge.new(dining_charge_params) @dining_charge.dining_facility_id = @settings_dining_facility.id + @dining_charge.minimum_free_time = DateTime.parse(dining_charge_params["minimum_free_time"]) + @dining_charge.charge_block = DateTime.parse(dining_charge_params["charge_block"]) + @dining_charge.time_rounding_block = DateTime.parse(dining_charge_params["time_rounding_block"]) + respond_to do |format| if @dining_charge.save if @table - format.html { redirect_to edit_settings_zone_table_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully created.' } + format.html { redirect_to settings_zone_table_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully created.' } else - format.html { redirect_to edit_settings_zone_room_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully created.' } + format.html { redirect_to settings_zone_room_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully created.' } end format.json { render :show, status: :created, location: @dining_charge } else @@ -50,15 +54,18 @@ class Settings::DiningChargesController < ApplicationController def update respond_to do |format| @dining_charge.dining_facility_id = @settings_dining_facility.id + @dining_charge.minimum_free_time = DateTime.parse(dining_charge_params["minimum_free_time"]) + @dining_charge.charge_block = DateTime.parse(dining_charge_params["charge_block"]) + @dining_charge.time_rounding_block = DateTime.parse(dining_charge_params["time_rounding_block"]) if @dining_charge.update(dining_charge_params) # @dining_charge.minimum_free_time = @dining_charge.minimum_free_time.to_datetime.advance(hours: +6, minutes: +30) # @dining_charge.charge_block = @dining_charge.charge_block.to_datetime.advance(hours: +6, minutes: +30) # @dining_charge.time_rounding_block = @dining_charge.time_rounding_block.to_datetime.advance(hours: +6, minutes: +30) # @dining_charge.save if @table - format.html { redirect_to edit_settings_zone_table_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully updated.' } + format.html { redirect_to settings_zone_table_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully updated.' } else - format.html { redirect_to edit_settings_zone_room_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully updated.' } + format.html { redirect_to settings_zone_room_path(@zone,@settings_dining_facility), notice: 'Dining charge was successfully updated.' } end format.json { render :show, status: :ok, location: @dining_charge } else diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 037767c9..27024d75 100755 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,19 +6,5 @@ module ApplicationHelper when :error then "alert alert-error fade-in" when :alert then "alert alert-error fade-in" end - end - - # For Pageless - # def pageless(total_pages, url=nil, container=nil) - # opts = { - # :totalPages => total_pages, - # :url => url, - # :loaderMsg => 'Loading more pages...', - # :loaderImage => image_path('load.gif') - # } - - # container && opts[:container] ||= container - - # javascript_tag("$('#{container}').pageless(#{opts.to_json});") - # end + end end diff --git a/app/models/dining_charge.rb b/app/models/dining_charge.rb index bfd25efc..555ba168 100755 --- a/app/models/dining_charge.rb +++ b/app/models/dining_charge.rb @@ -17,7 +17,7 @@ class DiningCharge < ApplicationRecord if charge_type == 'hr' block_count, price = DiningCharge.charges(dining_charges_obj, dining_minutes, 'hr') elsif charge_type == 'day' - block_count, price = charges(dining_charges_obj, dining_minutes, 'day') + block_count, price = DiningCharge.charges(dining_charges_obj, dining_minutes, 'day') end end return block_count, price @@ -27,40 +27,44 @@ class DiningCharge < ApplicationRecord end + # dining charges calculate def self.charges(chargesObj, dining_minutes, type) solid_price = 0 charge_block = DiningCharge.convert_to_minutes(chargesObj.charge_block.utc.localtime.strftime('%H:%M')) result = dining_minutes / charge_block if result.to_i < 1 - return result.to_i,chargesObj.unit_price + # for dining minute is under charge_block + return 1, result.to_i,chargesObj.unit_price elsif result.to_i >= 1 solid_price = result * chargesObj.unit_price remain_value = dining_minutes % charge_block - rounding_block = DiningCharge.convert_to_minutes(chargesObj.time_rounding_block.utc.localtime.strftime('%H:%M')) - roundingblock = remain_value / rounding_block + rounding_time = DiningCharge.convert_to_minutes(chargesObj.time_rounding_block.utc.localtime.strftime('%H:%M')) + roundingblock = remain_value / rounding_time if roundingblock.to_i < 1 - return result.to_i, DiningCharge.check_rounding(chargesObj, solid_price) + # no time rounding block + return result.to_i, DiningCharge.check_rounding(chargesObj, solid_price, roundingblock) else - solid_price += roundingblock * chargesObj.time_rounding_block_price - return result.to_i, DiningCharge.check_rounding(chargesObj, solid_price) - # remain_rounding = dining_minutes % charge_block - # if remain_rounding.to_i < 1 - # return DiningCharge.check_rounding(chargesObj, solid_price) - # else - # return solid_price - # end + solid_price += (roundingblock * chargesObj.time_rounding_block_price) + return result.to_i, DiningCharge.check_rounding(chargesObj, solid_price, roundingblock) end end end - def self.check_rounding(chargesObj,solid_price) - if chargesObj.time_rounding == "down" - return solid_price - else - return solid_price += chargesObj.time_rounding_block_price - end + # check for rounding and calculate with rounding price + def self.check_rounding(chargesObj,solid_price, roundingblock) + rounding_block_remain = roundingblock % 1 + if chargesObj.time_rounding == "down" + return solid_price + else + # check and calc for time rounding block for up + if rounding_block_remain > 0 + return solid_price += chargesObj.time_rounding_block_price + else + return solid_price + end + end end def self.time_diff(start_time, end_time) diff --git a/app/models/license.rb b/app/models/license.rb index b2682213..31403038 100755 --- a/app/models/license.rb +++ b/app/models/license.rb @@ -1,7 +1,7 @@ class License include HTTParty - base_uri "secure.smartsales.asia/api" + base_uri "provision.zsai.ws/api" attr_accessor :name, :address_1, :address_2, :township, :city, :country, :email, :phone, :fax, :logo, :subdomain, :plan_activation_date, :plan_next_renewal_date, :plan_max_products,:plan_max_customers, :plan_active_connections, @@ -13,14 +13,14 @@ class License def initialize(server = "", lookup = "") #this code is hard-code to reflect server mode - Very important. - self.server_mode = "cloud" + self.server_mode = ENV["SERVER_MODE"] if (server != "") self.class.base_uri server end - @secret = SecureRandom.hex(10) - @params = { query: { device: "SXlite", token: SECRETS_CONFIG['provision_key'] } } + # @secret = ENV["aes_key"] + # @params = { query: { device: "SX", token: SECRETS_CONFIG['provision_key'] } } end def shop_code @@ -29,11 +29,11 @@ class License else return self.subdomain.upcase end - end + end - def detail_with_local_cache(lookup, key) + def detail_with_local_cache(lookup, key, iv) ##Check from local redis - if available load local otherwise get from remote - cache_key = "store:license:#{key}:hostname" + cache_key = "#{lookup}:license:#{key}:hostname" # No Needs for current # @secret = key @@ -46,13 +46,11 @@ class License end Rails.logger.info "Cache key - " + cache_key.to_s - if cache_license.nil? ##change the d/e key # @options = { query: {device: "SXlite", lookup: lookup, skey: @secret, token: SECRETS_CONFIG['provision_key']} } - @params = { query: { device: "SXlite", token: SECRETS_CONFIG['provision_key']} } - - response = self.class.get("/request_license", @params) + @params = { query: { lookup_type: self.server_mode, lookup: lookup, encrypted_key: key, iv_key: iv} } + response = self.class.get("/subdomain", @params) @license = response.parsed_response if (@license["status"] == true) @@ -72,23 +70,56 @@ class License end Rails.logger.info 'API License' - - else - - @license = Marshal.load(cache_license) if cache_license - - Rails.logger.info 'Cache License' - - if (@license["status"] == true) - assign() - return true - end end - return false + end + + + def detail_with_local_file() + has_license = true #verify_license() + + if has_license + puts "VERIFIED" + end + + # if cache_license.nil? + # ##change the d/e key + # @params = { query: { lookup_type: self.server_mode, lookup: lookup, encrypted_key: key, iv_key: iv} } + + # response = self.class.get("/request_license", @params) + # @license = response.parsed_response + + # if (@license["status"] == true) + + # assign() + + # Rails.logger.info "License - " + response.parsed_response.to_s + + # Redis.current do |conn| + # ##Remote - store the remote response in local redis cache + # conn.set(cache_key, Marshal.dump(@license)) + # ##ADD to List to remove later + # conn.sadd("License:cache:keys", cache_key) + # end + + # return true + # end + + # Rails.logger.info 'API License' + + # else + # @license = Marshal.load(cache_license) if cache_license + + # Rails.logger.info 'Cache License' + + # if (@license["status"] == true) + # assign() + # return true + # end + # end + # return false end def detail - response = self.class.get("/subdomain", @options) @license = response.parsed_response @@ -104,9 +135,26 @@ class License return false end + def verify_license + api_token = read_license("api_token") + @options = { query: {lookup_type: "application", token: api_token} } + response = self.class.get("/verify", @options) + @varified = response.parsed_response + + Rails.logger.debug "License Remote Response - " + response.parsed_response.to_s + if (@varified["status"]) + if (!check_expired(@varified["plan_next_renewal_date"])) + return true + end + else + delete_license_file + end + return false + end + def check_remote_license(license_key) # @options = { query: {device: "cloud", key: license_key, skey: @secret, token: Rails.application.secrets.provision_key} } - @options = { query: {device: "SXlite", key: license_key, skey: @secret, token: SECRETS_CONFIG['provision_key']} } + @options = { query: {lookup_type: "application", encrypted_key: @secret, token: SECRETS_CONFIG['provision_key']} } response = self.class.get("/license", @options) @license = response.parsed_response @@ -120,7 +168,7 @@ class License end def verify_by_api_token(api_token) - @options = { query: {device: "SXlite", api_token: api_token, skey: @secret, token: SECRETS_CONFIG['provision_key']} } + @options = { query: {device: "SX", api_token: api_token, skey: @secret, token: SECRETS_CONFIG['provision_key']} } response = self.class.get("/verify", @options) @license = response.parsed_response @@ -135,15 +183,32 @@ class License #Load License is remove from the cloud license because - this license is must be validated against subdmain instead of license.data from file. - def expired? - if (self.plan_next_renewal_date < Date.today) + def check_expired(renewal_date) + if (renewal_date < Date.today) return true else return false end end + + def self.check_license_file + return File.exist?("config/license.yml") + end - private + # read line by key for license file + def read_license(key) + decrypted_line = "" + if File.exist?("config/license.yml") + File.open("config/license.yml").each do |line| + if line.include? (key) + decrypted_line_array = line.split(":") + decrypted_line = AESCrypt.decrypt(decrypted_line_array[1]) + end + end + end + end + + private def assign # self.name = @license["name"] # self.address_1 = @license["address_1"] diff --git a/app/models/my_aes_crypt.rb b/app/models/my_aes_crypt.rb new file mode 100644 index 00000000..7427a000 --- /dev/null +++ b/app/models/my_aes_crypt.rb @@ -0,0 +1,38 @@ +class MyAesCrypt + @cipher = "" + + def initialize + @cipher = OpenSSL::Cipher::Cipher.new(ENV["CIPHER_TYPE"]) + end + + def export_key(passphrase) + # We want a 256 bit key symetric key based on passphrase + digest = Digest::SHA256.new + key = digest.update(passphrase) + key = digest.digest + ENV['AES_KEY'] = cipher_key = key # stores the key in key, and also sets the generated key on the @cipher + ENV['AES_IV'] = cipher_iv = @cipher.random_iv # stores the iv in iv, and also sets the generated iv on the @cipher + return cipher_key, cipher_iv + end + + private + def encrypt(data) + cipher.encrypt + cipher.key = ENV["aes_key"] + cipher.iv = ENV["aes_iv"] + encrypted = cipher.update(data) + cipher.final + encrypted = Base64.encode64(encrypted) + return encrypted + end + + def decrypt(data) + cipher.decrypt + cipher.key = ENV["aes_key"] + cipher.iv = ENV["aes_iv"] + + # Start the decryption + decoded = Base64.decode64(data) + decrypted = cipher.update(decoded) + cipher.final + return decrypted + end +end \ No newline at end of file diff --git a/app/pdf/receipt_bill_pdf.rb b/app/pdf/receipt_bill_pdf.rb index 8f47b4fe..bcd3becf 100755 --- a/app/pdf/receipt_bill_pdf.rb +++ b/app/pdf/receipt_bill_pdf.rb @@ -145,9 +145,9 @@ class ReceiptBillPdf < Prawn::Document sale_items.each do |item| # check for item not to show if item.price != 0 - sub_total += (item.qty*item.unit_price) + sub_total += item.price #(item.qty*item.unit_price) - comment for room charges qty = item.qty - total_price = item.qty*item.unit_price + total_price = item.price #item.qty*item.unit_price - comment for room charges price = item.unit_price product_name = item.product_name diff --git a/app/views/install/_form.html.erb b/app/views/install/_form.html.erb index e165b51b..288c431a 100755 --- a/app/views/install/_form.html.erb +++ b/app/views/install/_form.html.erb @@ -1,17 +1,36 @@ -
-
- - - Name of business this system is license to + +
+
+ + + Name of business this system is license to +
+
+ + + Add License Key from Email +
+
+ + + First Employee who will be assign as administrator +
+
+ + +
-
- - - First Employee who will be assign as administrator +
+
+ + +
+
+ + +
-
- - +
+
- diff --git a/app/views/install/index.html.erb b/app/views/install/index.html.erb index 2c08db7e..0233f878 100755 --- a/app/views/install/index.html.erb +++ b/app/views/install/index.html.erb @@ -1,17 +1,11 @@
-
-
-
- +
-

New Restaurant Installation

+

License Activation


Welcome to new installation of SmartSales Restaurant Edition

Please provide us with following details to setup necessary user account and base system settings.

<%= render "install/form" %>
-
-
-
diff --git a/app/views/layouts/installation.html.erb b/app/views/layouts/installation.html.erb index 14cf9820..35b99bd3 100755 --- a/app/views/layouts/installation.html.erb +++ b/app/views/layouts/installation.html.erb @@ -10,11 +10,26 @@ <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + - -
+ +
<%= yield %>
diff --git a/app/views/origami/home/index.html.erb b/app/views/origami/home/index.html.erb index 3cb39508..607dbf4e 100755 --- a/app/views/origami/home/index.html.erb +++ b/app/views/origami/home/index.html.erb @@ -42,16 +42,14 @@ <% if table.status == 'occupied' %> <% if table.get_booking.nil? %>
-
- <%= table.get_booking %> +
Zone <%= table.zone_id %>
Table <%= table.name %> ( <%= table.seater %> Seat )
<% else %>
-
- <%= table.get_booking %> +
Zone <%= table.zone_id %>
Table <%= table.name %> ( <%= table.seater %> Seat )
diff --git a/config/initializers/sx.rb b/config/initializers/license.rb old mode 100755 new mode 100644 similarity index 56% rename from config/initializers/sx.rb rename to config/initializers/license.rb index aa52f14c..edb075ea --- a/config/initializers/sx.rb +++ b/config/initializers/license.rb @@ -1,4 +1,4 @@ -config = YAML.load_file(Rails.root.join("config/sx.yml")) +config = YAML.load_file(Rails.root.join("config/license.yml")) config.fetch(Rails.env, {}).each do |key, value| ENV[key.upcase] = value.to_s end \ No newline at end of file diff --git a/config/initializers/secrets.rb b/config/initializers/secrets.rb index 6ad1f1eb..a88ce110 100755 --- a/config/initializers/secrets.rb +++ b/config/initializers/secrets.rb @@ -1,6 +1,6 @@ -# config = YAML.load_file(Rails.root.join("config/smartsales.yml")) -# config.fetch(Rails.env, {}).each do |key, value| -# ENV[key.upcase] = value.to_s -# end +config = YAML.load_file("#{Rails.root}/config/secrets.yml") +config.fetch(Rails.env, {}).each do |key, value| + ENV[key.upcase] = value.to_s +end -SECRETS_CONFIG = YAML.load_file("#{Rails.root}/config/secrets.yml")[Rails.env] +# SECRETS_CONFIG = YAML.load_file("#{Rails.root}/config/secrets.yml")[Rails.env] diff --git a/config/license.yml b/config/license.yml new file mode 100644 index 00000000..f98a6e81 --- /dev/null +++ b/config/license.yml @@ -0,0 +1,14 @@ +development: + server_mode: cloud + license_key: IAAXHpbSWAfvlWGYpDoXvZdmuRABNGk + + +test: + sx_provision_url: "provision.test.ws/api" + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + server_mode: cloud + license_key: IAAXHpbSWAfvlWGYpDoXvZdmuRABNGk + diff --git a/config/secrets.yml b/config/secrets.yml index f81a9056..09555540 100755 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -11,8 +11,12 @@ # if you're sharing your code publicly. development: - secret_key_base: b61d85f8ed2a1a9e0eeece3443b3e8f838d002cc1d9f32115d8e93db920e2957adfedc57501d44741211538f3108b742cdeada87d5bfae796c53da1f90a3cd61 - provision_key: IAAXHpbSWAfvlWGYpDoXvZdmuRABNGk + secret_key_base: b61d85f8ed2a1a9e0eeece3443b3e8f838d002cc1d9f32115d8e93db920e2957adfedc57501d44741211538f3108b742cdeada87d5bfae796c53da1f90a3cd61 + sx_provision_url: provision.zsai.ws/api #192.168.1.94:3002 + server_mode: cloud + cipher_type: AES-256-CBC + aes_key: <%= ENV['AES_KEY'] %> + aes_iv: <%= ENV['AES_IV'] %> test: secret_key_base: 5c92143fd4a844fdaf8b22aba0cda22ef1fc68f1b26dd3d40656866893718ae5e58625b4c3a5dc86b04c8be0a505ec0ebc0be3bf52249a3d1e0c1334ee591cf0 @@ -20,6 +24,9 @@ test: # Do not keep production secrets in the repository, # instead read values from the environment. production: - secret_key_base: c4bc81065013f9a3506d385bcbd49586c42e586488144b0de90c7da36867de9fa880f46b5c4f86f0ce9b7c783bb5a73bdb0e5605a47716567294390e726d3e22 - provision_key: IAAXHpbSWAfvlWGYpDoXvZdmuRABNGk + secret_key_base: c4bc81065013f9a3506d385bcbd49586c42e586488144b0de90c7da36867de9fa880f46b5c4f86f0ce9b7c783bb5a73bdb0e5605a47716567294390e726d3e22 + sx_provision_url: provision.zsai.ws/api #192.168.1.94:3002 + server_mode: cloud + aes_key: <%= ENV['AES_KEY'] %> + aes_iv: <%= ENV['AES_IV'] %> diff --git a/config/sx.yml b/config/sx.yml deleted file mode 100755 index 1fc3a186..00000000 --- a/config/sx.yml +++ /dev/null @@ -1,14 +0,0 @@ -development: - server_mode: cloud #local - sx_provision_url: http://192.168.1.162:3005/api - - -test: - sx_provision_url: secure.smartsales.asia/api - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - server_mode: cloud - sx_provision_url: secure.smartsales.asia/api - diff --git a/lib/tasks/clear_data.rake b/lib/tasks/clear_data.rake index a761636e..3071132b 100755 --- a/lib/tasks/clear_data.rake +++ b/lib/tasks/clear_data.rake @@ -14,7 +14,7 @@ namespace :clear do ShiftSale.delete_all PaymentJournal.delete_all DiningFacility.update_all(status:'available') - CashierTerminal.update_all(is_currently_login: 1) + CashierTerminal.update_all(is_currently_login: 0) puts "Clear Data Done." end end