package mobile;
use Dancer ':syntax';

use FindBin qw( $Bin );
use lib "$Bin/../../../../lib";

use Encode qw( decode encode );
use Data::Dumper;
use App::POS::Config;
use App::POS::Check;
use App::POS::Area;
use App::POS::Machine::Server;
use App::POS::Machine::Station;
#use LWP::Simple qw();
use File::Remove qw( remove );
use File::Find qw( find );
use File::Slurp qw( read_file write_file read_dir );
use File::Copy qw( copy move );
use JSON qw( encode_json decode_json );
use POSIX qw( strftime );
use Text::CSV_XS;
use Date::Calc qw( Date_to_Time );
use Date::Manip::DM5 qw( UnixDate DateCalc DateCalc Date_Init ParseDate );
use Cache::Memcached::Fast;
#use App::POS::Utils qw( translate my_round my_conv date_based_on_closing_hour md5_from_file );
&Date_Init("Language=Portuguese", "DateFormat=non-US", "IntCharSet=1", "Tz=GMT");

my $DEBUG = 0;

$ENV{POS} = "/opt/pos";

our $VERSION = '0.1';

our $CFG = App::POS::Config->new( file => $ENV{POS}."/etc/config.ini" );
my $info = $CFG->load_fixed();

our $CLOSE_HOUR = undef;
our %MOBILE_CACHE = ();
our %AREAS = ();
our %EXTRAS = ();
our %CLERKS_INFO = ();
our %PRODUCTS_INFO = ();
our %MESSAGE_PRODUCTS = ();
our $MACHINE = undef;
our %AREA_FILTERS = ();
our $SUSPEND_AREA = undef;
our %AREAS_MD5 = ();

my $csv = Text::CSV_XS->new({ binary => 1, sep_char => '#', always_quote => 1, quote_char => '' });

if (exists $info->{STATION_NUMBER} && $info->{STATION_NUMBER} > 0) {
  $MACHINE = App::POS::Machine::Station->new( config => $CFG );
}
else {
  $MACHINE = App::POS::Machine::Server->new( config => $CFG );
}

$CLOSE_HOUR = $info->{END_BUSINESS_HOUR} || "05:00";
my @tmp = split /:/, $CLOSE_HOUR;
$CLOSE_HOUR .= ":00" if scalar @tmp == 2;

my $db = $MACHINE->database();

my $tmp = $db->select("SELECT id FROM products WHERE deleted = 0 AND is_extra = 1");
map { $EXTRAS{$_->{id}} = 1 } @$tmp;

$tmp = $db->select("SELECT * FROM products WHERE deleted = 0");
map { $PRODUCTS_INFO{$_->{id}} = $_ } @$tmp;

$tmp = $db->select("SELECT * FROM clerks WHERE deleted = 0");
map { $CLERKS_INFO{$_->{id}} = $_ } @$tmp;

$tmp = $db->select("SELECT * FROM products WHERE deleted = 0 AND is_message = ?", 1);
map { $MESSAGE_PRODUCTS{$_->{id}} = $_ } @$tmp;

my $areas = $MACHINE->areas();

foreach my $a (@$areas) {
  next unless $a->number() =~ /^\d+$/;
  $SUSPEND_AREA = $a->number() if $a->is_suspend();
  next unless $a->tables_number() > 0;

  $AREAS{$a->number()}->{tables} = $a->tables_number();
  $AREAS{$a->number()}->{start_table} = $a->start_table();
}

my $memc = Cache::Memcached::Fast->new({
  servers => [ { address => $MACHINE->server_ip().':11211', weight => 1 }, ],
  namespace => 'mobile:',
  connect_timeout => 0.1,
  io_timeout => 0.1,
  close_on_error => 1,
  #compress_threshold => 100_000,
  #compress_ratio => 0.9,
  compress_threshold => -1,
  compress_ratio => 0,
  max_failures => 3,
  failure_timeout => 1,
  nowait => 1,
  hash_namespace => 1,
  serialize_methods => [ \&JSON::encode_json, \&JSON::decode_json ],
});

get '/' => sub {
    template 'index';
};

# ########################################################################
# Clerks
# ########################################################################

# list clerks
get '/clerks' => sub {
  my $callback = params->{callback};

  my $db = $MACHINE->database();

  my $recs = $db->select("SELECT id, login FROM clerks WHERE deleted = 0 AND blocked_stations NOT LIKE '%99%'");
  my $error = scalar @$recs ? "" : "No records";

  my $info = { error => $error, data => $recs };

  if (defined $callback) {
    return $callback."(".&encode_json($info).");";
  }
  else {
    return &encode_json($info);
  }
};

# TODO
# login clerk
post '/clerks/:clerk_id/login' => sub {
  my $callback = params->{callback};
  my $clerk_id = params->{clerk_id};

  my $recs = [];
  my $info = { error => 'Not implemented', data => $recs };

  if (defined $callback) {
    return $callback."(".&encode_json($info).");";
  }
  else {
    return &encode_json($info);
  }
};

# TODO
# logout clerk
post '/clerks/:clerk_id/logout' => sub {
  my $callback = params->{callback};
  my $clerk_id = params->{clerk_id};

  my $recs = [];
  my $info = { error => 'Not implemented', data => $recs };

  if (defined $callback) {
    return $callback."(".&encode_json($info).");";
  }
  else {
    return &encode_json($info);
  }
};

# ########################################################################
# MENUS
# ########################################################################

get '/csv/menus' => sub {
  my $callback = params->{callback};

  my $db = $MACHINE->database();

  my $recs = $db->select("SELECT * FROM product_menus ORDER BY product_menu_id, step_number");
  my $info = "";

  foreach my $a (@$recs) {
    $info .= $a->{product_menu_id}."#".$a->{product_id}."#".$a->{step_number}."#".$a->{extra_price}."\@";
  }

  return decode("utf-8", $info);
};

get '/json/menus' => sub {
  header 'Access-Control-Allow-Origin' => '*';
  content_type("text/json");
  my $callback = params->{callback};

  my $db = $MACHINE->database();

  my $recs = $db->select("SELECT * FROM product_menus ORDER BY product_menu_id, step_number");
  my $info = "";

  foreach my $a (@$recs) {
    $info .= $a->{product_menu_id}."#".$a->{product_id}."#".$a->{step_number}."#".$a->{extra_price}."\@";
  }

  return to_json($info, { utf8 => 0 });
};

# ########################################################################
# CUSTOMER
# ########################################################################

# Returns a customer name by nif
get '/csv/customer' => sub {
  my $callback = params->{callback};

  my $customer_nif = params->{nif};

  my $db = $MACHINE->database();

  my $recs = $db->select("SELECT name from customers where nif ='" . $customer_nif . "'");
  my $info = "";

  foreach my $a (@$recs) {
    $info .= $a->{name};
  }

  return decode("utf-8", $info);
};

# ########################################################################
# Areas
# ########################################################################

get '/json/areas' => sub {
  my $callback = params->{callback};
  header 'Access-Control-Allow-Origin' => '*';

  _debug("/json/areas");

  my $areas = $MACHINE->areas();

  my $areas_filter = $info->{MOBILE_AREAS} || "";
  my %af = ();

  if ($areas_filter) {
    if (ref($areas_filter)) {
      map { $af{ $_ } = 1 } @$areas_filter;
    }
    else {
      $af{ $areas_filter } = 1;
    }
  }

  my $error = scalar @$areas ? "" : "No areas found";
  my $recs = [];

  my $info = "";

  my $cfg_info = $CFG->load_fixed();

  my %area_ask_persons_hash = ();
  my $ask_persons_mode = 0;

  if (exists $cfg_info->{AREA_ASK_PERSONS} && $cfg_info->{AREA_ASK_PERSONS}) {
    my @area_ask_persons = ref($cfg_info->{AREA_ASK_PERSONS}) eq 'ARRAY' ? @{$cfg_info->{AREA_ASK_PERSONS}} : ($cfg_info->{AREA_ASK_PERSONS});
    $ask_persons_mode = $cfg_info->{ASK_PERSONS_MODE};
    %area_ask_persons_hash = map { $_ => 1 } @area_ask_persons;
  }

  foreach my $a (@$areas) {
    next unless $a->number() =~ /^\d+$/;
    next unless $a->tables_number() > 0;

    my $auto_products_tmp = $a->automatic_products();
    my @auto_products = $auto_products_tmp ? split /;/, $auto_products_tmp : ();

    if ($areas_filter eq "") {
      push @$recs, {
        number => $a->number(),
        name => $a->name(),
        tables_number => $a->tables_number(),
        start_table => $a->start_table(),
        price_field => $a->price_field(),
        vat_field => $a->vat_field(),
        ask_persons_mode => $ask_persons_mode, 
        automatic_products => \@auto_products,
        ask_persons => exists $area_ask_persons_hash{$a->name()} ? 1 : 0,
        ask_table_to_card => $a->ask_table_to_card(),
        prices_wo_vat => $a->prices_wo_vat(),
      };
    }
    else {
      push @$recs, {
        number => $a->number(),
        name => $a->name(),
        tables_number => $a->tables_number(),
        start_table => $a->start_table(),
        price_field => $a->price_field(),
        vat_field => $a->vat_field(),
        automatic_products => \@auto_products,
        ask_persons_mode => $ask_persons_mode, 
        ask_persons => exists $area_ask_persons_hash{$a->name()} ? 1 : 0,
        ask_table_to_card => $a->ask_table_to_card(),
        prices_wo_vat => $a->prices_wo_vat(),
      } unless not defined $af{$a->name()};
    }
  }

  my %areas_hash;

  foreach my $a (@$recs) {
    $areas_hash{$a->{name}} = $a;
  }
  

  # LISTS AND HAPPY HOURS

  # my $recs = [];
  
  $cfg_info->{AREA_HAPPY_HOUR} ||= [];
  $cfg_info->{AREA_HAPPY_HOUR} = [ $cfg_info->{AREA_HAPPY_HOUR} ] unless ref($cfg_info->{AREA_HAPPY_HOUR});

  foreach my $h (@{$cfg_info->{AREA_HAPPY_HOUR}}) {
    # SALA,cerv,sell_price2,sell_vat1
    my @a = split /,/, $h;
    my $rec;
    eval {
      $rec = $db->select("SELECT * FROM lists WHERE kind = ? AND list_name = ?", 'H', $a[1]);
    };

    if (scalar $rec) {
      $rec->[0]->{area} = $a[0];
      $rec->[0]->{price} = $a[2];
      $rec->[0]->{vat} = $a[3];

      my $products_lists;
      eval {
        $products_lists = $db->select("SELECT * FROM products_lists WHERE list_id = ?", $rec->[0]->{id});
      };

      $rec->[0]->{list} = $products_lists;
      push @{$areas_hash{$rec->[0]->{area}}->{happy_hours}}, $rec->[0];
    }

    # push @$recs, $rec->[0];
  }

  
  $cfg_info->{AREA_PRODUCTS_LIST} ||= [];
  $cfg_info->{AREA_PRODUCTS_LIST} = [ $cfg_info->{AREA_PRODUCTS_LIST} ] unless ref($cfg_info->{AREA_PRODUCTS_LIST});

  foreach my $h (@{$cfg_info->{AREA_PRODUCTS_LIST}}) {
    # SALA,cerv,sell_price2,sell_vat1
    my @a = split /,/, $h;
    my $rec;
    eval {
      $rec = $db->select("SELECT * FROM lists WHERE kind = ? AND list_name = ?", 'L', $a[1]);
    };

    if (scalar $rec) {
      $rec->[0]->{area} = $a[0];
      # $rec->[0]->{price} = $a[2];
      # $rec->[0]->{vat} = $a[3];

      my $products_lists;
      eval {
        $products_lists = $db->select("SELECT * FROM products_lists WHERE list_id = ?", $rec->[0]->{id});
      };

      
      $rec->[0]->{list} = $products_lists;
      push @{$areas_hash{$rec->[0]->{area}}->{product_lists}}, $rec->[0];
    }

    # push @$recs, $rec->[0];
  }

    

  # END OF LISTS AND HAPPY HOURS

  return to_json($recs, { utf8 => 0 });
};

get '/json/area_total/:area_id' => sub {
  my $area_id = params->{area_id};
  header 'Access-Control-Allow-Origin' => '*';

  #_debug("/json/area_total/$area_id");

  my $info = {};
  my $dir = "/opt/pos/server/data/areas/$area_id";
  
  if (! -d $dir) {
    return to_json($info);
  }

  my $area_md5 = `find $dir -type f -print0 | xargs -0 cat | md5sum`;

  #if (exists $AREAS_MD5{$area_id} && $area_md5 eq $AREAS_MD5{$area_id}) {
  #  $info->{no_refresh} = 1;
  #}
  #else {
    $AREAS_MD5{$area_id} = $area_md5;

    find(sub{
      my $file = $_;
      #$File::Find::name
      #$File::Find::dir
      return unless -f $File::Find::name;

      # once upon a time, javascript created a table number
      # "undefined". This prevents that.
      if (/undefined/) {
        unlink $File::Find::name;
        return;
      }
      
      if ( $file =~ /\.lock$/ ) {
        my( $table_num ) = $file =~ /^(\d+)\./;

        if (defined $table_num && $table_num) {
          $info->{ $table_num }->{ locked } = 1; 
          $info->{ $table_num }->{ table_number } = $table_num;

          my $line = read_file($File::Find::name);

          if (defined $line && $line) {
            chop($line);
            my @a = split /#/, $line;

            if (scalar @a == 2) {
              $info->{ $table_num }->{ locked_station } = $a[0]; 
              $info->{ $table_num }->{ locked_clerk } = $a[1]; 
            }
          }
        }
      }
      elsif ( $file =~ /\.total$/ ) {
        my( $table_num ) = $file =~ /^(\d+)\./;

        if (defined $table_num && $table_num) {
          my $cnt = read_file($File::Find::name);
          chop($cnt);

          $info->{ $table_num }->{ total } = $cnt;
          $info->{ $table_num }->{ table_number } = $table_num;
        }
      }
      elsif ( $file =~ /\.bill$/ ) {
        my( $table_num ) = $file =~ /^(\d+)\./;

        if (defined $table_num && $table_num) {
          $info->{ $table_num }->{ bill } = 1; 
        }
      }
    }, ($dir));
  #}

  return to_json($info, { utf8 => 0 });
};

# list areas (CSV)
get '/csv/areas' => sub {
  my $callback = params->{callback};

  my $areas = $MACHINE->areas();

  my $areas_filter = $info->{MOBILE_AREAS} || "";
  my %af = ();

  if ($areas_filter) {
    if (ref($areas_filter)) {
      map { $af{ $_ } = 1 } @$areas_filter;
    }
    else {
      $af{ $areas_filter } = 1;
    }
  }

  my $error = scalar @$areas ? "" : "No areas found";
  my $recs = [];

  my $info = "";

  foreach my $a (@$areas) {
    next unless $a->number() =~ /^\d+$/;
    next unless $a->tables_number() > 0;

    if (%af) {
      $info .= $a->number()."#".$a->name()."\@" if exists $af{$a->name()};
    }
    else {
      $info .= $a->number()."#".$a->name()."\@";
    }
  }

  return decode("utf-8", $info);
};

get '/csv/areas_full' => sub {
  my $callback = params->{callback};

  my $areas = $MACHINE->areas();

  my $error = scalar @$areas ? "" : "No areas found";
  my $recs = [];

  my $info = "";

  foreach my $a (@$areas) {
    next unless $a->number() =~ /^\d+$/;
    next unless $a->tables_number() > 0;

    my $tables_str = "";

    my $start = $a->start_table();
    $start ||= 1;

    for (my $idx=$start; $idx<$start+$a->tables_number(); $idx++) {
      my $name_file = $MACHINE->dir_server()."/server/data/areas/".$a->number()."/".$idx.".name";

      if (-e $name_file) {
        my $name = read_file($name_file);
        $tables_str .= "$idx=$name,";
      }
      else {
        $tables_str .= "$idx=$idx,";
      }
    }

    $tables_str =~ s/,$//;

    $info .= $a->number()."=".$a->name()."#".$tables_str.'@';
  }

  $info =~ s/\@$//;

  # 1=Sala#1=1,2=Maria@2=Esplanada#40=40,41=41,42=Miguel

  return decode("utf-8", $info);
};


get '/json/report_tables' => sub {
  header 'Access-Control-Allow-Origin' => '*';

  content_type('text/json');

  my $clerk_id = params->{clerk_id};
  my $area_id = params->{area_id};

  #my $db = $MACHINE->database();

  my $info = "";

  my $dir_areas = $MACHINE->dir_server().'/server/data/areas';

  my @to_return;

  find( sub {
    return unless -d $File::Find::name;
    return if $File::Find::name eq $dir_areas;


    my $area = $_;

    if (defined $area_id) {
      return unless $area == $area_id;
    }

    find( sub {
      my $number = $_;
      return unless $number =~ /^\d+$/;
      return unless $number < 90000;
      my $file_check = $dir_areas."/".$area."/".$number;
      my $file_check_total = $dir_areas."/".$area."/".$number.".total";

      if (-e $file_check_total) {
        my $o = App::POS::Check->new( file => $file_check );


        my $data = $o->data();
        my $clerk_open = $data->{header}->[3];
        #my $clerk_name = exists $CLERKS_INFO->{$clerk_open} ? $CLERKS_INFO->{$clerk_open}->{login} : '<UNKNOWN>';

        if ($clerk_open eq $clerk_id) {
          my $area_o = App::POS::Area->new( machine => $MACHINE, number => $area );
          my $area_name = $area_o->name();

          my $total = $o->value();

          push @to_return, {
            area_name => $area_name,
            table_number => $number,
            total     => $total,
          };
        }
      }
    }, ( $dir_areas."/".$area ) );
  }, ( $dir_areas ) );

  # return decode("utf-8", $info);
  return to_json(\@to_return, { utf8 => 0 });
};


get '/csv/report_tables' => sub {
  my $clerk_id = params->{clerk_id};

  #my $db = $MACHINE->database();

  my $info = "";

  my $dir_areas = $MACHINE->dir_server().'/server/data/areas';

  find( sub {
    return unless -d $File::Find::name;
    return if $File::Find::name eq $dir_areas;

    my $area = $_;

    find( sub {
      my $number = $_;
      return unless $number =~ /^\d+$/;
      return unless $number < 90000;
      my $file_check = $dir_areas."/".$area."/".$number;
      my $file_check_total = $dir_areas."/".$area."/".$number.".total";

      if (-e $file_check_total) {
        my $o = App::POS::Check->new( file => $file_check );


        my $data = $o->data();
        my $clerk_open = $data->{header}->[3];
        #my $clerk_name = exists $CLERKS_INFO->{$clerk_open} ? $CLERKS_INFO->{$clerk_open}->{login} : '<UNKNOWN>';

        if ($clerk_open eq $clerk_id) {
          my $area_o = App::POS::Area->new( machine => $MACHINE, number => $area );
          my $area_name = $area_o->name();

          my $total = $o->value();

          #nome area#mesa#valor@
          $info .= $area_name."#".$number."#".$total.'@';
        }
      }
    }, ( $dir_areas."/".$area ) );
  }, ( $dir_areas ) );

  return decode("utf-8", $info);
};

get '/json/clerks' => sub {
  my $db = $MACHINE->database();
  header 'Access-Control-Allow-Origin' => '*';

  _debug("/json/clerks");

  my $recs = $db->select(qq{
    SELECT id, login, passwd, can_void_products, can_transfer_products, cant_close_checks,
      cant_use_others_tables, cant_suspend, can_transfer_tables, cant_close_others_tables, cant_print_bill, default_area
    FROM clerks
    WHERE deleted = 0 AND blocked_stations NOT LIKE '%99%'
    ORDER BY login
  });

  content_type('text/json');
  return to_json($recs, { utf8 => 0 });
};

get '/json/ping' => sub {
  header 'Access-Control-Allow-Origin' => '*';

  #_debug("/json/ping");

  return "OK - " . time;
};

# aqui pedro
get '/json/app_configs' => sub {
  my $db = $MACHINE->database();
  header 'Access-Control-Allow-Origin' => '*';

  _debug("/json/app_configs");

  my $lic = `curl 127.0.0.1:8088`;
  my @lic_data = split /\#\#/, $lic;

  my %results;

  # NOK##0#0##PTCERT#2015_06_30_10_02_40##ISDEMO#
  my $cfg_info = $CFG->load_fixed();
  $results{VAT_DEFAULT} = $cfg_info->{VAT_DEFAULT};
  $results{LANG_TRANSLATION} = $cfg_info->{LANG_TRANSLATION};
  $results{LICENSE_STATUS} = $lic_data[0];
  $results{ORDER_SEPARATORS} = $cfg_info->{ORDER_SEPARATORS} || [];

  content_type('text/json');
  return to_json(\%results, { utf8 => 0 });
};

get '/csv/clerks' => sub {
  my $db = $MACHINE->database();

  #my $recs = $db->select("SELECT id, login, passwd, can_void_products, can_transfer_products, cant_close_checks, cant_use_others_tables, cant_suspend FROM clerks WHERE deleted = 0 AND can_login = 0 ORDER BY login");

  my $recs = $db->select("SELECT id, login, passwd, can_void_products, can_transfer_products, cant_close_checks, cant_use_others_tables, cant_suspend FROM clerks WHERE deleted = 0 AND blocked_stations not like '%99%' ORDER BY login");

  my $info = "";

  foreach my $a (@$recs) {
    $info .= $a->{id}."#".$a->{login}."#".$a->{passwd}."#".$a->{can_void_products}."#".$a->{can_transfer_products}."#".$a->{cant_close_checks}."#".$a->{cant_use_others_tables}."#".$a->{cant_suspend}."\@";
  }

  return decode("utf-8", $info);
};

# list areas
get '/areas' => sub {
  my $callback = params->{callback};

  my $areas = $MACHINE->areas();

  my $error = scalar @$areas ? "" : "No areas found";
  my $recs = [];

  foreach my $a (@$areas) {
    next unless $a->number() =~ /^\d+$/;

    push @$recs, {
      number => $a->number(),
      name => $a->name(),
      price_field => $a->price_field(),
      vat_field => $a->vat_field(),
      is_splits => $a->is_splits(),
      is_consumption => $a->is_consumption(),
      tables_number => $a->tables_number(),
    };
  }

  if (defined $callback) {
    return $callback."(".&encode_json( { error => $error, data => $recs } ).");";
  }
  else {
    my $str =  &encode_json( { error => $error, data => $recs } );
    print STDERR $str;
    return &encode_json( { error => $error, data => $recs } );
  }
};

# list tables/checks of an area
get '/areas/:area_id' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};

  my $error = "";
  my $recs = [];

  if (! defined $area_id) {
    $error = "Missing 'area_id' parameter";
  }
  else {
    my $area = App::POS::Area->new( machine => $MACHINE, number => $area_id );
    $recs = $area->checks_info();
  }

  if (defined $callback) {
    return $callback."(".&encode_json( { error => $error, data => $recs } ).");";
  }
  else {
    return &encode_json( { error => $error, data => $recs } );
  }
};

# ########################################################################
# Groups
# ########################################################################

get '/csv/print_check/:area_id/:check_id/:clerk_id' => sub {
  header 'Access-Control-Allow-Origin' => '*';

  my %params = params();
  # print STDERR Dumper(\%params);

  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};
  my $client_name = params->{client_name} || "";
  my $client_nif = params->{client_nif} || "";

  $client_name = encode("utf8", $client_name);

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  _debug("/csv/print_check/$area_id/$check_id/$clerk_id");

  my $db = $MACHINE->database();

  #my( $station, $area, $is_internal, $dont_print, $check ) = $file =~ /\/print_notclosed_check#(\d+)#(\d+)#(\d)#(\d)#(.*?)\.txt$/;
  #print STDERR "### print_notclosed_check -> $file -> $station -> $area -> $is_internal -> $dont_print -> $check\n";

  my $recs = $db->select(qq{
    SELECT * FROM clerks WHERE id = ?
  }, $clerk_id);

  my $file_print = "";

  if (scalar @$recs) {
    if ($recs->[0]->{cant_print_bill} eq '1' && $recs->[0]->{cant_print_bill_internal} eq '1') {
      return;
    }

    if ($recs->[0]->{cant_print_bill} eq '1') {
      $file_print = $MACHINE->dir_tmp()."/print_notclosed_check#".$info->{STATION_NUMBER}."#$area_id#1#0#$check_id#1.txt";
    }
    else {
      $file_print = $MACHINE->dir_tmp()."/print_notclosed_check#".$info->{STATION_NUMBER}."#$area_id#0#0#$check_id#1.txt";
    }
  }
  else {
    $file_print = $MACHINE->dir_tmp()."/print_notclosed_check#".$info->{STATION_NUMBER}."#$area_id#0#0#$check_id#1.txt";
  }

  # save name or nif to print bill
  if ($client_name || $client_nif) {
    my $check_o = App::POS::Check->new( file => $check_file );
    $check_o->{data}->{header}->[12] = $client_name;
    $check_o->{data}->{header}->[13] = $client_nif;
    $check_o->save();
  }

  _debug("bill! ".$check_file.".bill");

  write_file($file_print, "1\n");
  write_file($check_file.".bill", "1\n");
  #write_file("/opt/pos/server/data/tmp/checksrefresh.txt", "1\n");
  &copy( $check_file, $check_file.".toprint" );
  unlink("$check_file.lock");
};

# list parent groups
get '/json/groups' => sub {
  my $db = $MACHINE->database();
  header 'Access-Control-Allow-Origin' => '*';

  _debug("/json/groups");

  my $get_products = params->{get_products} || 0;

  my $q_select = '';
  my $q_where = '';
  my $q_group = '';

  if ($get_products) {
    $q_select .= ', p.id, p.name, p.sell_vat1, p.sell_vat2, p.sell_price1, p.sell_price2, p.sell_price3, p.sell_price4, p.sell_price5, p.sell_price_menu, p.is_message, p.is_extra, p.barcode, p.handy_plu, p.is_menu, p.is_weight, p.ask_price, p.which_extras';
    # $q_select .= ', p.*';
    $q_where .= ' AND p.deleted = 0';
    $q_group .= '';
  }

  my $recs = $db->select(qq{
    SELECT sf.name AS sub_name, sf.id AS sub_id, f.id AS top_id, f.name AS top_name, f.button_bg_color AS top_bg_color, sf.button_bg_color AS sub_bg_color, sf.priority AS sub_priority, f.priority AS top_priority $q_select
    FROM product_groups pg
    LEFT JOIN products p ON (pg.product_id = p.id)
    LEFT JOIN groups sf ON (pg.group_id = sf.id)
    LEFT JOIN groups f ON (sf.parent = f.id)
    WHERE p.deleted = 0 AND f.deleted = 0 AND sf.deleted = 0 $q_where
    -- GROUP BY sf.id, f.id, p.id $q_group
    ORDER BY f.priority DESC, f.name, sf.priority DESC, sf.name, p.priority DESC, p.name
  });

  my $product_menus;
  eval {
    $product_menus = $db->select(qq{
      SELECT pm.product_menu_id, pm.step_number, pm.extra_price, pm.id AS product_menu_row_id
        $q_select
      FROM product_menus pm
      LEFT JOIN products p ON (pm.product_id = p.id)
    });
  };

  my %product_menus_hash = ();

  foreach (@$product_menus) {
    $product_menus_hash{$_->{product_menu_id}}->{$_->{step_number}}->{step} = $_->{step_number};
    push @{$product_menus_hash{$_->{product_menu_id}}->{$_->{step_number}}->{products}}, $_;
  }

  my $message_products = $db->select(qq{
    SELECT *
    FROM products p
    WHERE p.deleted = 0 AND p.is_message = 1
    ORDER BY p.priority DESC, p.name ASC
  });

  foreach my $mp (@$message_products) {
    $mp->{sub_name} = '-';
    $mp->{sub_id} = 0;
    $mp->{top_name} = '-';
    $mp->{top_id} = 0;

    push @$recs, $mp;
  }

  my $recs3 = [];
  my $recs2 = [];
  my %control = ();
  my $new_version = params->{new_version} || 0;
  my %groups = ();
  my $idx = 0;

  my %did = ();

  foreach my $r (@$recs) {
    if (exists $did{ $r->{sub_id}."#".$r->{top_id}."#".$r->{id} }) {
      next;
    }

    $did{ $r->{sub_id}."#".$r->{top_id}."#".$r->{id} } = 1;

    if (not exists $groups{$r->{top_id}}) {
      $groups{$r->{top_id}}->{priority}     = $r->{top_priority};
      $groups{$r->{top_id}}->{top_id}       = $r->{top_id};
      $groups{$r->{top_id}}->{top_name}     = $r->{top_name};
      $groups{$r->{top_id}}->{top_bg_color} = sprintf("#%06x", $r->{top_bg_color} || '16777215');
      $groups{$r->{top_id}}->{idx} = $idx;
    }
    if (not exists $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}) {
      $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{priority}     = $r->{sub_priority};
      $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{sub_id}       = $r->{sub_id};
      $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{sub_name}     = $r->{sub_name};
      $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{sub_bg_color} = sprintf("#%06x", $r->{sub_bg_color} || '16777215');
      $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{idx} = $idx;
    }

    $r->{idx} = $idx;


    if ($get_products) {
      if ($r->{is_menu} && exists $product_menus_hash{$r->{id}}) {
        $r->{menu_items} = $product_menus_hash{$r->{id}};
      }
      push @{$groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{products}}, $r;
      $groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{total_products} = scalar @{$groups{$r->{top_id}}->{subfamilies}->{$r->{sub_id}}->{products}};
    }

    $idx++;
  }

  foreach my $a (@$recs) {
    my $key = $a->{top_id} . "#" . $a->{sub_id};
    next if exists $control{$key};
    $control{$key} = 1;

    $a->{sub_name} = encode("utf8", decode("utf8", $a->{sub_name}));
    $a->{top_name} = encode("utf8", decode("utf8", $a->{top_name}));

    push @$recs2, $a;
  }

  if ($new_version) {
    return decode("utf8", to_json(\%groups));
  }
  else {
    return decode("utf8", to_json($recs2));
  }
  #return to_json($recs2, { utf8 => 0 });
};

# list parent groups
get '/csv/groups' => sub {
  my $db = $MACHINE->database();

=pod
  my $recs = $db->select(qq{
    SELECT g1.id AS sub_id, g1.name AS sub_name, g1.parent AS sub_parent,
      g2.id AS top_id, g2.name AS top_name, g2.parent AS top_parent
    FROM groups g1
    LEFT JOIN groups g2 ON (g1.parent = g2.id)
    WHERE g1.deleted = 0 AND g2.deleted = 0
    ORDER BY g1.parent, g2.parent
  });
=cut

  my $recs = $db->select(qq{
    SELECT sf.name AS sub_name, sf.id AS sub_id, f.id AS top_id, f.name AS top_name
    FROM product_groups pg
    LEFT JOIN products p ON (pg.product_id = p.id)
    LEFT JOIN groups sf ON (pg.group_id = sf.id)
    LEFT JOIN groups f ON (sf.parent = f.id)
    WHERE p.deleted = 0 AND f.deleted = 0 AND sf.deleted = 0
    GROUP BY sf.id, f.id, p.id
    ORDER BY f.id, sf.id
  });

  my $info = "";
  my $last_top_id = "";
  my %control = ();

  foreach my $a (@$recs) {
    my $key = $a->{top_id} . "#" . $a->{sub_id};
    next if exists $control{$key};
    $control{$key} = 1;

    #$a->{top_name} = decode("utf8", $a->{top_name});
    #$a->{sub_name} = decode("utf8", $a->{sub_name});
    $a->{top_name} =~ s/\//-/g;
    $a->{sub_name} =~ s/\//-/g;

    if ($last_top_id ne $a->{top_id}) {
      $info .= "\@".$a->{top_id}."\$\$".$a->{top_name}."#";
      $last_top_id = $a->{top_id};
    }

    $info .= $a->{sub_id}."\$\$".$a->{sub_name}."#";
  }

  $info =~ s/^.// if $info;

  return decode("utf-8", $info);
};

# list groups that has extras inside
get '/csv/groups_extras' => sub {
  my $db = $MACHINE->database();

  my $recs = $db->select(qq{
    SELECT sf.name AS sub_name, sf.id AS sub_id, f.id AS top_id, f.name AS top_name
    FROM product_groups pg
    LEFT JOIN products p ON (pg.product_id = p.id)
    LEFT JOIN groups sf ON (pg.group_id = sf.id)
    LEFT JOIN groups f ON (sf.parent = f.id)
    WHERE p.is_extra = 1 AND p.deleted = 0 AND f.deleted = 0 AND sf.deleted = 0
    GROUP BY sf.id, f.id, p.id
    ORDER BY f.id, sf.id
  });

  my $info = "";
  my $last_top_id = "";
  my %control = ();

  foreach my $a (@$recs) {
    my $key = $a->{top_id} . "#" . $a->{sub_id};
    next if exists $control{$key};
    $control{$key} = 1;

    #$a->{top_name} = decode("utf8", $a->{top_name});
    #$a->{sub_name} = decode("utf8", $a->{sub_name});
    $a->{top_name} =~ s/\//-/g;
    $a->{sub_name} =~ s/\//-/g;

    if ($last_top_id ne $a->{top_id}) {
      $info .= "\@".$a->{top_id}."\$\$".$a->{top_name}."#";
      $last_top_id = $a->{top_id};
    }

    $info .= $a->{sub_id}."\$\$".$a->{sub_name}."#";
  }

  $info =~ s/^.// if $info;

  return decode("utf-8", $info);
};

# list parent groups
get '/groups' => sub {
  my $callback = params->{callback};
  my $db = $MACHINE->database();

  my $recs = $db->select("SELECT id, name FROM groups WHERE deleted = 0 AND parent = 0");
  my $error = scalar @$recs ? "" : "No records";

  my $info = { error => $error, data => $recs };

  if (defined $callback) {
    return $callback."(".&encode_json($info).");";
  }
  else {
    return &encode_json($info);
  }
};

# list groups of a parent group
get '/groups/:group_id' => sub {
  my $callback = params->{callback};
  my $group_id = params->{group_id};

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  if (! defined $group_id) {
    $error = "Didn't receive a 'group_id' parameter";
  }
  else {
    $recs = $db->select("SELECT id, name FROM groups WHERE deleted = 0 AND parent = ?", $group_id);
    $error = scalar @$recs ? "" : "No records";
  }

  if (defined $callback) {
    return $callback."(".&encode_json({ error => $error, data => $recs }).");";
  }
  else {
    return &encode_json({ error => $error, data => $recs });
  }
};

# list products inside a group
get '/groups/:group_id/products' => sub {
  my $callback = params->{callback};
  my $group_id = params->{group_id};

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  if (! defined $group_id) {
    $error = "Didn't receive a 'group_id' parameter";
  }
  else {
    $recs = $db->select(qq{
      SELECT p.*
      FROM product_groups pg
      LEFT JOIN products p ON (pg.product_id = p.id)
      WHERE p.deleted = 0 AND pg.group_id = ?
    }, $group_id);
    $error = scalar @$recs ? "" : "No records";
  }

  if (defined $callback) {
    return $callback."(".&encode_json({ error => $error, data => $recs }).");";
  }
  else {
    return &encode_json({ error => $error, data => $recs });
  }
};

# ########################################################################
# Products
# ########################################################################

# search products
get '/products/search/:term' => sub {
  my $callback = params->{callback};
  my $term = params->{term};

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  if (! defined $term) {
    $error = "Didn't receive a 'term' parameter";
  }
  else {
    $recs = $db->select(qq{
      SELECT * FROM products WHERE deleted = 0 AND id = ? OR name LIKE ?
    }, $term, '%'.$term.'%');
    $error = scalar @$recs ? "" : "No records";
  }

  if (defined $callback) {
    return $callback."(".&encode_json({ error => $error, data => $recs }).");";
  }
  else {
    return &encode_json({ error => $error, data => $recs });
  }
};

any '/teste' => sub {
  my $conta = params->{conta};

  print STDERR "######## $conta\n";

  return "OK";
};

get '/json/product_lists' => sub {
  header 'Access-Control-Allow-Origin' => '*';

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  my $cfg_info = $CFG->load_fixed();
  $cfg_info->{AREA_PRODUCTS_LIST} ||= [];
  $cfg_info->{AREA_PRODUCTS_LIST} = [ $cfg_info->{AREA_PRODUCTS_LIST} ] unless ref($cfg_info->{AREA_PRODUCTS_LIST});

  foreach my $h (@{$cfg_info->{AREA_PRODUCTS_LIST}}) {
    # SALA,cerv,sell_price2,sell_vat1
    my @a = split /,/, $h;
    my $rec = $db->select("SELECT * FROM lists WHERE kind = ? AND list_name = ?", 'L', $a[1]);

    if (scalar @$rec) {
      $rec->[0]->{area} = $a[0];
      # $rec->[0]->{price} = $a[2];
      # $rec->[0]->{vat} = $a[3];
    }

    push @$recs, $rec->[0];
  }

  return decode("utf8", to_json($recs));
};

get '/json/happy_hours' => sub {
  header 'Access-Control-Allow-Origin' => '*';

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  my $cfg_info = $CFG->load_fixed();
  $cfg_info->{AREA_HAPPY_HOUR} ||= [];
  $cfg_info->{AREA_HAPPY_HOUR} = [ $cfg_info->{AREA_HAPPY_HOUR} ] unless ref($cfg_info->{AREA_HAPPY_HOUR});

  foreach my $h (@{$cfg_info->{AREA_HAPPY_HOUR}}) {
    # SALA,cerv,sell_price2,sell_vat1
    my @a = split /,/, $h;
    my $rec = $db->select("SELECT * FROM lists WHERE kind = ? AND list_name = ?", 'H', $a[1]);

    if (scalar @$rec) {
      $rec->[0]->{area} = $a[0];
      $rec->[0]->{price} = $a[2];
      $rec->[0]->{vat} = $a[3];

      my $products_lists = $db->select("SELECT * FROM products_lists WHERE list_id = ?", $rec->[0]->{id});

      $rec->[0]->{list} = $products_lists;
    }


    push @$recs, $rec->[0];
  }

  return decode("utf8", to_json($recs));
};

get '/json/products/:area_id' => sub {
  my $area_id = params->{area_id};
  header 'Access-Control-Allow-Origin' => '*';

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  my $a = App::POS::Area->new( machine => $MACHINE, number => $area_id );

  my $cfg_info = $CFG->load_fixed();

  my $price_field = $a->price_field();
  my $vat_field = $a->vat_field();

  my $area_name = $a->name();

  my $happy_hour_name = "";
  my $happy_hour_price = "";
  my $happy_hour_vat = "";
  $cfg_info->{AREA_HAPPY_HOUR} ||= [];

  if (scalar @{$cfg_info->{AREA_HAPPY_HOUR}}) {
    for (@{$cfg_info->{AREA_HAPPY_HOUR}}) {
      my @toks = split /,/, $_;

      if ($toks[0] eq $area_name) {
        $happy_hour_name = $toks[1];
        $happy_hour_price = $toks[2];
        $happy_hour_vat = $toks[3];
        last;
      }
    }
  }

  my %products_happy = ();
  my $filter_by_happy = 0;

  if ($happy_hour_name) {
    #
    # Happy Hours
    #

    my $r = $db->select(qq{
      SELECT pl.*, l.*, l.kind as list_kind, pl.kind as line_kind
      FROM products_lists pl
      LEFT JOIN lists l ON (pl.list_id = l.id)
      LEFT JOIN product_groups pg ON (pl.product_id = pg.product_id)
      WHERE l.kind = 'H' AND l.for_reporting = 0 AND list_name = ?
    }, $happy_hour_name);

    my $start_hour;
    my $end_hour;

    for (@$r) {
      $start_hour = $_->{start_hour};
      $end_hour   = $_->{end_hour};

      if ($_->{line_kind} eq 'P') {
        $products_happy{$_->{product_id}} = 1;
      }
      else {
        my $rr = $db->select("SELECT product_id FROM product_groups WHERE group_id = ?", $_->{product_id});
        map { $products_happy{$_->{product_id}}= 1 } @$rr;
      }
    }

    if ($start_hour && $end_hour) {
      my $cur_date   = ParseDate(&strftime("%Y-%m-%d %H:%M:%S", localtime));
      my $start_date = ParseDate(&strftime("%Y-%m-%d", localtime)." ".$start_hour);
      my $end_date   = ParseDate(&strftime("%Y-%m-%d", localtime)." ".$end_hour);

      my $delta_start = DateCalc($cur_date, $start_date);
      my $delta_end   = DateCalc($end_date, $cur_date);

      if ($delta_start =~ /^\-/ && $delta_end =~ /^\-/) {
        $filter_by_happy = 1;
      }
    }
  }

  #
  # Product Lists
  #

  my $cur_area_list = "";
  my $lists = $info->{AREA_PRODUCTS_LIST} || "";
  my %product_filters = ();

  if (exists $AREA_FILTERS{$area_name}) {
    %product_filters = %{$AREA_FILTERS{$area_name}};
  }
  else {
    if ($lists) {
      if (ref($lists)) {
        foreach my $l (@$lists) {
          my @t = split /,/, $l;
          $cur_area_list = $t[1] if $t[0] eq $area_name;
        }
      }
      else {
        my @t = split /,/, $lists;
        $cur_area_list = $t[1] if $t[0] eq $area_name;
      }
    }

    if ($cur_area_list) {
      my $l = $db->select(qq{
        SELECT pl.*
        FROM products_lists pl
        LEFT JOIN lists l ON (pl.list_id = l.id)
        WHERE l.list_name = ?
      }, $cur_area_list);

      my $groups = "";

      for (@$l) {
        if ($_->{kind} eq 'P') {
          $product_filters{ $_->{product_id} } = 1;
        }
        else {
          $groups .= $_->{product_id}.", ";
        }
      }

      if ($groups) {
        $groups =~ s/, $//;

        my $prods = $db->select(qq{
          SELECT pg.product_id
          FROM product_groups pg
          WHERE pg.group_id IN ($groups)
        });

        for (@$prods) {
          $product_filters{ $_->{product_id} } = 1;
        }
      }

      $AREA_FILTERS{$area_name} = \%product_filters;
    }
  }

  my $grps = $db->select("SELECT product_id, group_id FROM product_groups");
  my %groups = ();

  for (@$grps) {
    push @{ $groups{$_->{product_id}} }, $_->{group_id}
  }

  my $info = "";

  $recs = $db->select(qq{
    SELECT id, barcode, handy_plu, name, button_text, $price_field AS sell_price, $vat_field AS sell_vat, is_weight, ask_price, is_extra, is_menu, is_message, which_extras
    FROM products
    WHERE deleted = 0
    ORDER BY name
  });

  my %names = ();
  my @to_return;

  foreach my $a (@$recs) {
    # filter products, if needed
    if (%product_filters) {
      next unless exists $product_filters{ $a->{id} };
    }

    $a->{button_text} ||= '';
    $a->{name} = (defined $a->{button_text} and $a->{button_text} ne '') ? encode('utf8', decode('utf8', $a->{button_text})): encode("utf8", decode("utf8", $a->{name}));
    # $a->{name} = encode("utf8", decode("utf8", $a->{name}));


    $a->{is_weight} ||= 0;
    $a->{ask_price} ||= 0;

    if ( $a->{is_weight} and $a->{ask_price} ) {
      $a->{is_weight_and_price} = 1;
      $a->{is_weight} ||= 0;
      $a->{ask_price} ||= 0;
    }

    $a->{barcode} ||= "";
    $a->{sell_price} ||= 0;
    # continuar aqui 

    $a->{sell_vat} ||= $cfg_info->{VAT_DEFAULT};

    my $groups_str = "";

    if (exists $groups{$a->{id}}) {
      $a->{groups} = $groups{$a->{id}};
      #$groups_str = join(",", @{$groups{$a->{id}}});
    }

    $a->{is_extra} ||= "";
    $a->{is_extra} = "" if $a->{is_extra} eq "--";
    $a->{is_menu} ||= "";
    $a->{is_menu} = "" if $a->{is_menu} eq "--";
    $a->{is_message} ||= "";
    $a->{is_message} = "" if $a->{is_message} eq "--";

    #if (! $a->{is_extra} && ! $a->{is_menu} && ! $a->{is_message} && ! $a->{sell_price}) {
    #if (! $a->{is_extra} && ! $a->{is_menu} && ! $a->{is_message}) {
    #  next;
    #}

    my $name = $a->{name};
    #$name = decode("utf8", $name);

    if (exists $names{$name}) {
      $names{$name}++;
      $name = $name.$names{$name};
    }
    else {
      $names{$name} = 1;
    }

    # apply happy hour price and vat if avbl
    if ($filter_by_happy && exists $products_happy{$a->{id}}) {
      $a->{sell_price} = $a->{$happy_hour_price};
      $a->{sell_vat} = $a->{$happy_hour_vat};
    }

    my $handy_plu = 0;

    if ($a->{handy_plu} =~ /^\d+$/ && length($a->{handy_plu}) <= 4) {
      $handy_plu = $a->{handy_plu};
    }

    #$a->{groups} = $groups_str;

    push @to_return, $a;
  }

  return decode("utf8", to_json(\@to_return));
};

get '/csv/products/:area_id' => sub {
  my $area_id = params->{area_id};

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  my $a = App::POS::Area->new( machine => $MACHINE, number => $area_id );

  my $cfg_info = $CFG->load_fixed();

  my $price_field = $a->price_field();
  my $vat_field = $a->vat_field();

  my $area_name = $a->name();

  my $happy_hour_name = "";
  my $happy_hour_price = "";
  my $happy_hour_vat = "";
  $cfg_info->{AREA_HAPPY_HOUR} ||= [];

  if (scalar @{$cfg_info->{AREA_HAPPY_HOUR}}) {
    for (@{$cfg_info->{AREA_HAPPY_HOUR}}) {
      my @toks = split /,/, $_;

      if ($toks[0] eq $area_name) {
        $happy_hour_name = $toks[1];
        $happy_hour_price = $toks[2];
        $happy_hour_vat = $toks[3];
        last;
      }
    }
  }

  my %products_happy = ();
  my $filter_by_happy = 0;

  if ($happy_hour_name) {
    #
    # Happy Hours
    #

    my $r = $db->select(qq{
      SELECT pl.*, l.*, l.kind as list_kind, pl.kind as line_kind
      FROM products_lists pl
      LEFT JOIN lists l ON (pl.list_id = l.id)
      LEFT JOIN product_groups pg ON (pl.product_id = pg.product_id)
      WHERE l.kind = 'H' AND l.for_reporting = 0 AND list_name = ?
    }, $happy_hour_name);

    my $start_hour;
    my $end_hour;

    for (@$r) {
      $start_hour = $_->{start_hour};
      $end_hour = $_->{end_hour};

      if ($_->{line_kind} eq 'P') {
        $products_happy{$_->{product_id}} = 1;
      }
      else {
        my $rr = $db->select("SELECT product_id FROM product_groups WHERE group_id = ?", $_->{product_id});
        map { $products_happy{$_->{product_id}}= 1 } @$rr;
      }
    }

    if ($start_hour && $end_hour) {
      my $cur_date = ParseDate(&strftime("%Y-%m-%d %H:%M:%S", localtime));
      my $start_date = ParseDate(&strftime("%Y-%m-%d", localtime)." ".$start_hour);
      my $end_date = ParseDate(&strftime("%Y-%m-%d", localtime)." ".$end_hour);

      my $delta_start = DateCalc($cur_date, $start_date);
      my $delta_end = DateCalc($end_date, $cur_date);

      if ($delta_start =~ /^\-/ && $delta_end =~ /^\-/) {
        $filter_by_happy = 1;
      }
    }
  }

  #
  # Product Lists
  #

  my $cur_area_list = "";
  my $lists = $info->{AREA_PRODUCTS_LIST} || "";
  my %product_filters = ();

  if (exists $AREA_FILTERS{$area_name}) {
    %product_filters = %{$AREA_FILTERS{$area_name}};
  }
  else {
    if ($lists) {
      if (ref($lists)) {
        foreach my $l (@$lists) {
          my @t = split /,/, $l;
          $cur_area_list = $t[1] if $t[0] eq $area_name;
        }
      }
      else {
        my @t = split /,/, $lists;
        $cur_area_list = $t[1] if $t[0] eq $area_name;
      }
    }

    if ($cur_area_list) {
      my $l = $db->select(qq{
        SELECT pl.*
        FROM products_lists pl
        LEFT JOIN lists l ON (pl.list_id = l.id)
        WHERE l.list_name = ?
      }, $cur_area_list);

      my $groups = "";

      for (@$l) {
        if ($_->{kind} eq 'P') {
          $product_filters{ $_->{product_id} } = 1;
        }
        else {
          $groups .= $_->{product_id}.", ";
        }
      }

      if ($groups) {
        $groups =~ s/, $//;

        my $prods = $db->select(qq{
          SELECT pg.product_id
          FROM product_groups pg
          WHERE pg.group_id IN ($groups)
        });

        for (@$prods) {
          $product_filters{ $_->{product_id} } = 1;
        }
      }

      $AREA_FILTERS{$area_name} = \%product_filters;
    }
  }

  my $grps = $db->select("SELECT product_id, group_id FROM product_groups");
  my %groups = ();

  for (@$grps) {
    push @{ $groups{$_->{product_id}} }, $_->{group_id}
  }

  my $info = "";

  $recs = $db->select(qq{
    SELECT *, $price_field AS sell_price, $vat_field AS sell_vat FROM products WHERE deleted = 0 ORDER BY name
  });

  my %names = ();

  foreach my $a (@$recs) {
    # filter products, if needed
    if (%product_filters) {
      next unless exists $product_filters{ $a->{id} };
    }

    $a->{barcode} ||= "";
    $a->{sell_price} ||= 0;
    $a->{sell_vat} ||= 0;

    my $groups_str = "";

    if (exists $groups{$a->{id}}) {
      $groups_str = join(",", @{$groups{$a->{id}}});
    }

    $a->{is_extra} ||= "";
    $a->{is_extra} = "" if $a->{is_extra} eq "--";
    $a->{is_menu} ||= "";
    $a->{is_menu} = "" if $a->{is_menu} eq "--";
    $a->{is_message} ||= "";
    $a->{is_message} = "" if $a->{is_message} eq "--";

    #if (! $a->{is_extra} && ! $a->{is_menu} && ! $a->{is_message} && ! $a->{sell_price}) {
    #if (! $a->{is_extra} && ! $a->{is_menu} && ! $a->{is_message}) {
    #  next;
    #}

    my $name = $a->{name};
    #$name = decode("utf8", $name);

    if (exists $names{$name}) {
      $names{$name}++;
      $name = $name.$names{$name};
    }
    else {
      $names{$name} = 1;
    }

    # apply happy hour price and vat if avbl
    if ($filter_by_happy && exists $products_happy{$a->{id}}) {
      $a->{sell_price} = $a->{$happy_hour_price};
      $a->{sell_vat} = $a->{$happy_hour_vat};
    }

    my $handy_plu = 0;

    if ($a->{handy_plu} =~ /^\d+$/ && length($a->{handy_plu}) <= 4) {
      $handy_plu = $a->{handy_plu};
    }

    $info .= $a->{id}."#".$name."#".sprintf("%.2f", $a->{sell_price})."#".sprintf("%.2f", $a->{sell_vat})."#".$a->{barcode}."#".$groups_str."#".$a->{is_weight}."#".$a->{is_extra}."#".$a->{ask_price}."#".$a->{is_message}."#".$a->{is_menu}."#".$a->{parent_product}."#".$handy_plu."\@";
  }

  return decode("utf-8", $info);
};

get '/products/:area_id' => sub {
  my $area_id = params->{area_id};
  my $callback = params->{callback};

  my $db = $MACHINE->database();

  my $error = "";
  my $recs = [];

  # TODO: FALTA PREENCHER O sell_price COM O PRECO CORRECTO DA AREA

  my $a = App::POS::Area->new( machine => $MACHINE, number => $area_id );
  my $price_field = $a->price_field();

  $recs = $db->select(qq{
    SELECT *, $price_field AS sell_price FROM products WHERE deleted = 0 ORDER BY name
  });

  $error = scalar @$recs ? "" : "No records";

  if (defined $callback) {
    return $callback."(".&encode_json({ error => $error, data => $recs }).");";
  }
  else {
    return &encode_json({ error => $error, data => $recs });
  }
};

# TODO
# add a product to a check
post '/:area_id/:check_id/product/add' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
};

# TODO
# remove a product from a check
post '/:area_id/:check_id/product/del' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
};

get '/csv/checks/:area_id/:check_id/:clerk_id/:new_area_id/:new_check_id/move' => sub {
  my $area_id      = params->{area_id};
  my $check_id     = params->{check_id};
  my $clerk_id     = params->{clerk_id};
  my $new_area_id  = params->{new_area_id};
  my $new_check_id = params->{new_check_id};
  my $conta        = params->{conta} || "";
  my $ids          = params->{ids} || "";

  print STDERR "CONTA: $conta\n";
  print STDERR "IDS: #$ids#\n";

  my %ids_to_move = ();

  if ($ids) {
    my @ids = split /,/, $ids;
    map { $ids_to_move{$_} = 1 } @ids;
  }

  my $wipe_original = scalar keys %ids_to_move ? 0 : 1;

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";
  my $new_check_file = "$checks_dir/$new_area_id/$new_check_id";

  if (-e $new_check_file) {
    if (-s $new_check_file > 95) {
      return "ERROR=not_empty_check";
    }
  }

  if (-e "$new_check_file.lock") {
    my $str = read_file($new_check_file.".lock");
    my( $station, $clerk ) = $str =~ /^(\d+)#(\d+)/;
  }

  my $orig_check = App::POS::Check->new( file => $check_file );
  my $ttt = $orig_check->data();

  my $check = App::POS::Check->new( file => $new_check_file );
  my $data = $check->data();

  my $date = &strftime("%Y-%m-%d %H:%M:%S", localtime);
  my $hour = &strftime("%H:%M:%S", localtime);

  my $new_data = {};
  $new_data->{header} = $data->{header};
  $new_data->{header} ||= [];

  $new_data->{details} ||= [];
  $new_data->{details_deleted} ||= [];

  # check is empty, fill a new header for it
  if (! scalar @{ $new_data->{header} } || ! scalar @{ $data->{details} }) {
    my $business_date = &_date_based_on_closing_hour($date, $CLOSE_HOUR);
    $new_data->{header} = [];
    push @{ $new_data->{header} }, $check->generate_empty_header($date, $business_date, $clerk_id, $area_id);
  }

  my @lines = split /\@/, $conta;
  my $line_num = 0;

  my @orig_details;

  foreach my $line (@lines) {
    next if $line =~ /^_____/;
    my @toks = split /_/, $line;

    my( $prod_id, $qtd, $price, $gb_id ) = ( undef, undef, undef, undef );
    my $is_new = 0;
    my $vat = 0;
    my $holded = 0;

    my $rand = &_random_id();

    #print STDERR "#######> line_num: ".$line_num."\n";

    if (! scalar keys %ids_to_move) {
      #print STDERR "MOVE!!!!!!\n";
    }
    else {
      if (exists $ids_to_move{ $line_num }) {
        #print STDERR "MOVE!!!!!!\n";
      }
      else {
        push @orig_details, $orig_check->{data}->{details}->[$line_num];
        $line_num++;
        #print STDERR "NAO MOVE!!!!!!\n";
        next;
      }
    }

    $line_num++;

    # produto novo adicionado
    if (scalar @toks == 5) {
      ( $prod_id, $qtd, $price, $vat ) = ( $toks[1], $toks[2], $toks[3], $toks[4] );
      $is_new = 1;
    }
    # produto já tem gambas id
    else {
      ( $prod_id, $qtd, $price, $vat, $gb_id ) = ( $toks[1], $toks[2], $toks[3], $toks[4], $toks[5] );
      #$gb_id =~ s/\s/+/g;
      $rand = $gb_id;
      $holded = 1;
    }

    #print STDERR "prod_id: $prod_id, qtd: $qtd, price: $price, vat: $vat, gb_id: $gb_id\n";

    my @r = ( $line_num, $prod_id, $hour, $qtd, $price, 0, $vat, $clerk_id, "", "", "", "", 0, "", 0, 1, 1, $rand );

    # not holded products that were removed in the pda
    if (! $gb_id && $qtd < 0) {
      next;
    }

    if ($qtd < 0 && ! $is_new) {
      $qtd = $qtd*-1;
      $r[3] = $qtd;
      push @{$new_data->{details_deleted}}, \@r;
    }
    else {
      push @{$new_data->{details}}, \@r;
    }
  }

  $check->set_data($new_data);
  $check->save();

  # remove ENTIRE original check
  if ($wipe_original) {
    print STDERR "WILL REMOVE: $check_file\n";
    $orig_check->delete();
  }
  # original won't be wiped, keep details
  else {
    if (scalar @orig_details) {
      $orig_check->{data}->{details} = \@orig_details;
      $orig_check->save();
    }
    else {
      $orig_check->delete();
    }
  }

  # unlock check
  unlink("$check_file.lock");
  unlink("$new_check_file.lock");

  return "OK";
};

get '/json/checks/:area_id/:check_id/:clerk_id/open' => sub {
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};

  content_type('text/json');

  header 'Access-Control-Allow-Origin' => '*';

  # use memcache for table locking to avoid fast requests
  my $cache_key = 'locked_table#'.$area_id.'#'.$check_id;
  my $is_locked = $memc->get($cache_key);

  if (defined $is_locked && $is_locked) {
    return to_json({ error => 'check_locked' }, { utf8 => 0 });
  }

  $memc->set($cache_key, 1, 3);

  _debug("/json/checks/$area_id/$check_id/$clerk_id/open");

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  my $areas = $MACHINE->areas();
  my %i = ();

  foreach my $a (@$areas) {
    next unless $a->number() =~ /^\d+$/;
    next unless $a->tables_number() > 0;

    $i{$a->number()}->{tables} = $a->tables_number();
    $i{$a->number()}->{start_table} = $a->start_table();
  }

  my $area = App::POS::Area->new( machine => $MACHINE, number => $area_id );

  $AREAS{$area->number()}->{start_table} ||= 1;

  if ($check_id >= $AREAS{$area->number()}->{start_table} && $check_id <= $AREAS{$area->number()}->{start_table}+$AREAS{$area->number()}->{tables}) {
  } else {
    return to_json({ error => 'invalid_check' }, { utf8 => 0 });
  }

  my $check = App::POS::Check->new( file => $check_file );
  my $data = $check->data();
  my $recs = [];
  my $error = "";

  $data->{header} ||= [];
  $data->{details} ||= [];

  # valid check
  if (defined $data) {
    # locks check
    my $station_open = 99;

    if (-e "$check_file.lock") {
      my $str = read_file("$check_file.lock");
      my( $station, $clerk ) = $str =~ /^(\d+)#(\d+)/;

      if ($clerk ne $clerk_id) {
        return to_json({ error => 'check_locked' }, { utf8 => 0 });
      }
    }

    my $date = &strftime("%Y-%m-%d %H:%M:%S", localtime);
    my $hour = &strftime("%H:%M:%S", localtime);

    # check is empty, fill a new header for it
    if (! scalar @{ $data->{header} } || ! scalar @{ $data->{details} }) {
      my $business_date = &_date_based_on_closing_hour($date, $CLOSE_HOUR);
      $data->{header} = [];
      push @{ $data->{header} }, $check->generate_empty_header($date, $business_date, $clerk_id, $area_id);
    }

    $data->{header}->[2] ||= -1;

    # supersivor
    $CLERKS_INFO{$clerk_id}->{used_for_training} ||= 0;

    if (! $CLERKS_INFO{$clerk_id}->{used_for_training}) {
      if ($data->{header}->[2] != $clerk_id && $CLERKS_INFO{$clerk_id}->{cant_use_others_tables} == 1) {
        return to_json({ error => 1, message => "ERROR - Access denied"});
      }
    }

    write_file("$check_file.lock", "$station_open#$clerk_id\n");

    _refresh_tables($MACHINE);

    #write_file("/opt/pos/server/data/tmp/checksrefresh.txt", "1\n");

    #[ '0', '111', '11:00:46', '1.00', '1', '0', '13', '6', '', '', '', '', '0', '', '', '1', '1', '7.9849619E+7' ]
    #0 - Line Number
    #1 - Product Id
    #2 - Hour
    #3 - Quantity
    #4 - Price
    #5 - Discount
    #6 - Vat
    #7 - Clerk
    #8 - ??
    #9 - ??
    #10 - ??
    #11 - Discount Txt
    #12 - Station
    #13 - Text Extra
    #14 - Inside Menu
    #15 - Was holded already
    #16 - Price used on the sale

    my $recs = [];
    my $vat_default = $info->{VAT_DEFAULT} || 0;

    foreach my $d (@{ $data->{details} }) {
      next unless defined $d->[0] && $d->[0] =~ /^\d+$/;
      next unless defined $d->[1] && $d->[1] =~ /^\d+$/;

      $d->[4] = 0 if $d->[4] eq '';
      $d->[6] ||= $vat_default; 

      my $name = $PRODUCTS_INFO{$d->[1]}->{name} || "";

      if ($DEBUG) {
        _debug("  detail! idx: $d->[0], product: $name, hour: $d->[2], qtty: $d->[3], price: $d->[4], gbi: $d->[-1]");
      }

      push @$recs, {
        prod_name => $name,
        line_num => $d->[0],
        prod_id => $d->[1],
        qtty => sprintf("%.3f", $d->[3]),
        price => sprintf("%.2f", $d->[4]),
        vat => sprintf("%.2f", $d->[6]),
        is_extra => ($d->[13] ? 1 : 0),
        uniq_id => $d->[-1],
        clerk_id => $d->[7],
        hour => $d->[2],
        is_inside_menu => $d->[14],
      };
    }

    if (! scalar @$recs) {
      return "";
    }
    else {
      $recs = { details => $recs, headers => $data->{header} };
      $data->{header}->[12] = decode("utf8", $data->{header}->[12]);

      return to_json($recs, { utf8 => 0 });
    }
  }
  else {
    return to_json({ error => 'invalid_check' }, { utf8 => 0 });
  }

  if ($error) {
    return to_json({ error => $error }, { utf8 => 0 });
  }
};

get '/csv/checks/:area_id/:check_id/:clerk_id/close' => sub {
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  header 'Access-Control-Allow-Origin' => '*';

  _debug("/csv/checks/$area_id/$check_id/$clerk_id/close");

  if (-e "$check_file.lock") {
    my $str = read_file("$check_file.lock");
    my( $station, $clerk ) = $str =~ /^(\d+)#(\d+)/;

    if ($station eq '99') {
      unlink("$check_file.lock");
    }
  }

  if (-e "$check_file.total") {
    my $total = read_file("$check_file.total") || "";

    if ($total ne '') {
      $total = $total*1;

      my $check = App::POS::Check->new( file => $check_file );
      my $data = $check->data();

      if ($total == 0 && ! scalar @{$data->{details}} && ! scalar @{$data->{details_deleted}}) {
        unlink "$check_file.total";
      }
    }
  }

  _refresh_tables($MACHINE);

  #my $refresh_file = "/opt/pos/server/data/tmp/checksrefresh.txt";
  #write_file($refresh_file, "1\n");
  return "OK";
};

# ########################################################################
# Checks
# ########################################################################

any '/csv/checks/:area_id/:check_id/:clerk_id/save' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};
  my $conta = params->{conta};

  header 'Access-Control-Allow-Origin' => '*';

  return "" unless defined $conta;

  print STDERR "#### area_id: $area_id, check_id: $check_id, conta: $conta\n";

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  unlink("$check_file.lock");

  return "OK";
};

any '/csv/checks/:area_id/:check_id/:clerk_id/verify' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};
  my $conta = params->{conta};

  # check_missing
  # check_lock_missing

  header 'Access-Control-Allow-Origin' => '*';

  return "" unless defined $conta;

  _debug("/csv/checks/$area_id/$check_id/$clerk_id/verify");

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  my $check = App::POS::Check->new( file => $check_file );
  my $data = $check->data();

  my @lines = split /\@/, $conta;
  my $had_gbis = undef;

  foreach my $line (@lines) {
    my @toks = split /_/, $line;

    if ($toks[-1]) {
      $had_gbis = 1;
      last;
    }
  }

  content_type('text/json');

  # se o pedido ja tem produtos com gbi e o ficheiro da conta nao existe no disco e nao estamos numa transferencia
  # algo errado se passou... 
  if ($had_gbis && ! -e $check_file) {
    _debug("erro, conta tinha produtos com GBI e ja nao existia no disco... vou ignorar o pedido...");
    return to_json({ error => 'check_missing' }, { utf8 => 0 });
  }

  if (! -e "$check_file.lock") {
    _debug("pedido feito mas conta nao bloqueada, vou ignorar...");
    return to_json({ error => 'check_lock_missing' }, { utf8 => 0 });
  }

  return to_json({ error => '' }, { utf8 => 0 });
};

any '/csv/checks/:area_id/:check_id/:clerk_id/hold' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};
  my $conta = params->{conta};
  my $transfer = params->{transfer} || 0;
  my $new_version = params->{new_version} || "";
  my $print_invoice = params->{print_invoice} || 0;

  header 'Access-Control-Allow-Origin' => '*';

  return "" unless defined $conta;

  _debug("/csv/checks/$area_id/$check_id/$clerk_id/hold (transfer: $transfer)");

  #print STDERR "#### area_id: $area_id, check_id: $check_id, conta: $conta\n";

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  my $check = App::POS::Check->new( file => $check_file );
  my $data = $check->data();

  my $date = &strftime("%Y-%m-%d %H:%M:%S", localtime);
  my $hour = &strftime("%H:%M:%S", localtime);

  my $new_data = {};
  $new_data->{extra_data} = $data->{extra_data};
  $new_data->{header} = $data->{header};
  $new_data->{header} ||= [];

  $new_data->{details} ||= [];
  $new_data->{details_deleted} = $data->{details_deleted};

  # check is empty, fill a new header for it
  if (! scalar @{ $new_data->{header} } || ! scalar @{ $data->{details} }) {
    my $business_date = &_date_based_on_closing_hour($date, $CLOSE_HOUR);
    $new_data->{header} = [];
    push @{ $new_data->{header} }, $check->generate_empty_header($date, $business_date, $clerk_id, $area_id);
  }

  my @lines = split /\@/, $conta;
  my $line_num = 0;
  my $did_payment = undef;
  my $customer_nif = undef;
  my $customer_name = undef;
  my $doc_type = undef;
  my $had_gbis = undef;

  foreach my $line (@lines) {
    my @toks = split /_/, $line;
    #print STDERR Dumper(\@toks);

    if ($toks[0] eq 'close') {
      $did_payment = $toks[1];
      $customer_nif = $toks[2];
      $customer_name = encode("utf8", $toks[3]);
      next;
    }

    if ($toks[0] eq 'persons') {
      $new_data->{header}->[15] = $toks[1];
      next;
    }

    if ($toks[0] eq 'tabletocard') {
      $new_data->{extra_data}->{custom_table_number} = $toks[1];
      next;
    }

    my( $prod_id, $qtd, $price, $gb_id, $is_menu ) = ( undef, undef, undef, undef, undef );
    my $is_new = 0;
    my $vat = 0;
    my $holded = 0;

    my $rand = &_random_id();

    my $tmp_is_new = 0;

    if (scalar @toks == 7 && $toks[-1] eq '0') {
      $tmp_is_new = 1;
    }
    elsif (scalar @toks == 6 || (scalar @toks > 7 && ! $toks[6])) {
      $tmp_is_new = 1;
    }

    my $line_clerk_id = "";

    if ($new_version) {
      # if gb_id is defined, product isn't new
      $tmp_is_new = 1 unless $toks[-1];

      # produto novo adicionado
      if ($tmp_is_new) {
        ( $prod_id, $qtd, $price, $vat, $is_menu, $line_clerk_id, $hour ) = ( $toks[1], $toks[2], $toks[3], $toks[4], $toks[5], $toks[6], $toks[7] );
        $is_menu =~ s/^\s+$//;
        $is_new = 1;
      }
      # produto já tem gambas id
      else {
        $had_gbis = 1;

        ( $prod_id, $qtd, $price, $vat, $is_menu, $line_clerk_id, $hour, $gb_id ) = ( $toks[1], $toks[2], $toks[3], $toks[4], $toks[5], $toks[6], $toks[7], $toks[8] );
        $is_menu =~ s/^\s+$//;
        $rand = $gb_id;
        $holded = 1;
      }
    }
    else {
      $line_clerk_id = $clerk_id;

      # produto novo adicionado
      if ($tmp_is_new) {
        ( $prod_id, $qtd, $price, $vat, $is_menu ) = ( $toks[1], $toks[2], $toks[3], $toks[4], $toks[5] );
        $is_menu =~ s/^\s+$//;
        $is_new = 1;
      }
      # produto já tem gambas id
      else {
        $had_gbis = 1;

        ( $prod_id, $qtd, $price, $vat, $is_menu, $gb_id ) = ( $toks[1], $toks[2], $toks[3], $toks[4], $toks[5], $toks[6] );
        $is_menu =~ s/^\s+$//;
        #$gb_id =~ s/\s/+/g;
        $rand = $gb_id;
        $holded = 1;
      }
    }

    my $msg_txt = "";

    # foi um produto mensagem que foi pedido, a msg vem no campo IVA
    if (exists $MESSAGE_PRODUCTS{ $prod_id }) {
      $msg_txt = $vat;
      $vat = 0;
    }
    else {
      $vat = $vat*1;
    }

    my( $is_menu_parent ) = $is_menu =~ /^M#(\d+)$/;
    $is_menu_parent ||= 0;

    my $voided_qtd = -1;

    if ($qtd =~ /x/) {
      my ( $voided_qtd1, $orig_qtd ) = split /x/, $qtd;
      $voided_qtd = $voided_qtd1;
      $qtd = $orig_qtd - $voided_qtd;
    }

    my @r;

    if ($msg_txt) {
      @r = ( $line_num, $prod_id, $hour, $qtd, $price, 0, $vat, $line_clerk_id, "", "", "", "", 99, $msg_txt.";", 0, 1, 1, $rand );
    }
    elsif (exists $EXTRAS{ $prod_id }) {
      @r = ( $line_num, $prod_id, $hour, $qtd, $price, 0, $vat, $line_clerk_id, "", "", "", "", 99, "EXTRA", 0, 1, 1, $rand );
    }
    elsif ($is_menu_parent) {
      @r = ( $line_num, $prod_id, $hour, $qtd, $price, 0, $vat, $line_clerk_id, "", "", "", "", 99, "MENU", $is_menu_parent, 1, 1, $rand );
    }
    else {
      @r = ( $line_num, $prod_id, $hour, $qtd, $price, 0, $vat, $line_clerk_id, "", "", "", "", 99, "", 0, 1, 1, $rand );
    }

    # not holded products that were removed in the pda
    if (! $gb_id && $qtd < 0) {
      next;
    }

    if ($voided_qtd != -1) {
      my $void_hour = &strftime("%H:%M:%S", localtime);

      my @voided_row = @r;

      $voided_row[3]  = $voided_qtd;
      $voided_row[2]  = $voided_row[2] . "@" . $void_hour;
      $voided_row[10] = 2;
      $voided_row[7] = $clerk_id;

      push @{$new_data->{details_deleted}}, \@voided_row;
    }

    # ignore zero quantities
    next unless $qtd*1 > 0;

    push @{$new_data->{details}}, \@r;

    $line_num++;
  }

  _refresh_tables($MACHINE);

  # se o pedido ja tem produtos com gbi e o ficheiro da conta nao existe no disco e nao estamos numa transferencia
  # algo errado se passou... 
  if ($had_gbis && ! -e $check_file && ! $transfer) {
    _debug("erro, conta tinha produtos com GBI e ja nao existia no disco... vou ignorar o pedido...");
    content_type('text/json');
    return to_json({ error => 'check_missing' }, { utf8 => 0 });
  }

  if (! -e "$check_file.lock") {
    _debug("pedido feito mas conta nao bloqueada, vou ignorar...");
    content_type('text/json');
    return to_json({ error => 'check_lock_missing' }, { utf8 => 0 });
  }

  $check->set_data($new_data);

  $new_data->{details_deleted} ||= [];
  $new_data->{details} ||= [];

  # this means the check was totaly voided and had products begore
  if (scalar @{ $new_data->{details_deleted} } && ! scalar @{ $new_data->{details} }) {
    $doc_type = 3;
    $did_payment = 1;
  }

  $check->save();

  if ($DEBUG) {
    foreach my $d (@{$new_data->{details}}) {
      # 0#49#16:32:50#1#44.50#0#23#23####0#99##0#1#1#201609231632440284270753190992
      my $name = $PRODUCTS_INFO{$d->[1]}->{name} || "";
      _debug("detail! idx: $d->[0], product: $name, hour: $d->[2], qtty: $d->[3], price: $d->[4], gbi: $d->[-1]");
    }
  }

  write_file("$check_file.99.hold", "1\n");
  &copy( $check_file, $check_file.".99.hold.check" );

  open(NNN, "> $check_file.refresh");
  print NNN "1\n";
  close(NNN);

  if (-e "$check_file.lock") {
    my $str = read_file("$check_file.lock");
    my( $station, $clerk ) = $str =~ /^(\d+)#(\d+)/;

    if ($station eq '99') {
      unlink("$check_file.lock");
    }
  }

  # unlock check
  #unlink("$check_file.lock");

  if ($transfer && -e "$check_file.bill" && ! scalar @{ $new_data->{details} }) {
    unlink "$check_file.bill";
  }

  if (-e "$check_file.total") {
    local $/ = undef;

    my $total = read_file("$check_file.total");

    if ($total ne '') {
      $total = $total*1;

      if ($total == 0 && ! scalar @{$new_data->{details}} && ! scalar @{$new_data->{details_deleted}}) {
        unlink "$check_file.total";
      }
    }
  }

  #my $refresh_file = "/opt/pos/server/data/tmp/checksrefresh.txt";
  #write_file($refresh_file, "1\n");

  $info->{PRINT_CHECK_AFTER_HOLD} ||= 0;
  $info->{PRINT_CHECK_BEFORE_FINALIZE} ||= 0;
  my $file_print = "";

  my $clerk_info = exists $CLERKS_INFO{$clerk_id} ? $CLERKS_INFO{$clerk_id} : undef;

  if (defined $clerk_info) {
    if ($clerk_info->{cant_print_bill} eq '1' && $clerk_info->{cant_print_bill_internal} eq '1') {
      $file_print = "";
    }
    else {
      if ($clerk_info->{cant_print_bill} eq '1') {
        $file_print = $MACHINE->dir_tmp()."/print_notclosed_check#".$info->{STATION_NUMBER}."#$area_id#1#0#$check_id#1.txt";
      }
      else {
        $file_print = $MACHINE->dir_tmp()."/print_notclosed_check#".$info->{STATION_NUMBER}."#$area_id#0#0#$check_id#1.txt";
      }
    }
  }
  else {
    $file_print = $MACHINE->dir_tmp()."/print_notclosed_check#".$info->{STATION_NUMBER}."#$area_id#0#0#$check_id#1.txt";
  }

  if ($file_print) {
    # payment
    if ($did_payment) {
      if ($info->{PRINT_CHECK_BEFORE_FINALIZE}) {
        _debug("bill before payment! ".$check_file.".bill");
        &copy( $check_file, $check_file.".toprint" );
        write_file($file_print, "1\n");
        write_file($check_file.".bill", "1\n");
      }
    }
    # bill
    else {
      if ($info->{PRINT_CHECK_AFTER_HOLD}) {
        _debug("bill after hold! ".$check_file.".bill");
        &copy( $check_file, $check_file.".toprint" );
        write_file($file_print, "1\n");
        write_file($check_file.".bill", "1\n");
      }
    }
  }

  if (defined $did_payment) {
    _debug("payment! clerk_id: $clerk_id, check_file: $check_file, nif: $customer_nif, customer_name: $customer_name, doc_type: $doc_type, print_invoice: $print_invoice");
    &_check_finalize($clerk_id, $did_payment, $check, $check_file, $customer_nif, $customer_name, $doc_type, $print_invoice);
  }

  return "OK";
};

sub _check_finalize {
  my( $clerk_id, $media, $check, $check_file, $nif, $name, $doc_type, $print ) = @_;

  # $media = 0 -> suspend
  # $media = 1 -> cash
  # $media = 2 -> mb

  my $data = $check->data();
  my $headers = $data->{header};
  my $details = $data->{details} || [];
  my $footers = $data->{footers} || [];
  my $extra_data = $data->{extra_data};

  my $cfg_info = $CFG->load_fixed();

  $nif ||= "";
  $name ||= "";

  # doc_type 3 is a void (when all was voided from the check). When we have it
  # use it and do nothing like below
  if ($doc_type != 3) {
    # FACTUS
    if ($cfg_info->{LANG} eq 'FACTUS') {
      if (! $nif && ! $name) {
        $doc_type = 90;
      }
      else {
        $doc_type = 5;
        $extra_data->{invoice_is_payed} = 1;
      }
    }
    # OTHERS
    else {
      if (! $nif && ! $name) {
        $doc_type = 7;
        $extra_data->{invoice_is_payed} = 1;
      }
      else {
        $doc_type = 5;
      }
    }
  }

  my $area = $headers->[7];

  $headers->[3] = $clerk_id; # clerk close
  $headers->[1] = &strftime("%Y-%m-%d %H:%M:%S", localtime); # time close
  #$headers->[5] = "99"; # station close

  # used for voids
  if (defined $doc_type) {
    $headers->[8] = $doc_type;
  }

  # business date based on close date
  $headers->[23] = &_date_based_on_closing_hour($headers->[1], $CLOSE_HOUR);

  $extra_data->{original_area} ||= "";
  $extra_data->{from_mobile} = 1;

  if ($extra_data->{original_area} ne '') {
    $headers->[6] = $extra_data->{original_area};
    $headers->[7] = $extra_data->{original_area};
  }

  if ($media == 0) {
    $extra_data->{original_area} = $headers->[6];

    my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
    my $area_dir = "$checks_dir/$SUSPEND_AREA";
    my $new_id = &_find_check_nextid($area_dir);

    my $new_check_file = $area_dir."/".$new_id;
    my $new_check_o = App::POS::Check->new( file => $new_check_file );
    my $new_data = $new_check_o->data();
    $new_data->{header} = $headers;
    $new_data->{details} = $details;
    $new_data->{footers} = $footers;
    $new_data->{extra_data} = $extra_data;

    $new_check_o->save();
    $check->delete();
  }
  else {
    $headers->[13] = $nif;
    $headers->[12] = $name;

    $check->save();

    if ($media == 1) {
      push @{ $footers }, [
        'cash', $check->value(), '',
      ];
    } 
    elsif ($media == 2) {
      push @{ $footers }, [
        'MB', $check->value(), '',
      ];
    }

    $data->{footers} = $footers;

    $check->save();

=pod
  ' save name in headers
  headers[18] = ME.GetCheckName()
=cut

    write_file($check_file.".close.print.".$print, $check->value()."\n");

    if (-e $check_file.".deleted") {
      &move( $check_file.".deleted", $check_file.".close.deleted" );
    }

    if (-e $check_file) {
      &move( $check_file, $check_file.".close" );
    }

    unlink $check_file.".bill";
    unlink $check_file.".lock";
  }
}

sub _find_check_nextid {
  my $dir = shift;
  my $id = 1;

  my $f = $dir."/nextid";

  if (-e $f) {
    my $tmpid = read_file($dir."/nextid");
    $tmpid =~ s/\n//g;
    $tmpid =~ s/\r//g;
    $tmpid ||= 1;
    $tmpid++;
    $id = $tmpid;
  }

  write_file($f, "$id\n");

  return $id;
}

# lock a check
get '/checks/:area_id/:check_id/:clerk_id/lock' => sub {
  my $callback = params->{callback};
  my $area_id = params->{area_id};
  my $check_id = params->{check_id};
  my $clerk_id = params->{clerk_id};

  header 'Access-Control-Allow-Origin' => '*';

  my $checks_dir = $MACHINE->dir_server()."/server/data/areas";
  my $check_file = "$checks_dir/$area_id/$check_id";

  my $check = App::POS::Check->new( file => $check_file );
  my $data = $check->data();
  my $recs = [];
  my $error = "";

  # 'header' => [ '2011-05-04 11:00:44', '2011-05-04 11:01:23', '6', '6', '0', '0', '1', '1', '1', '0', '', '', '', '', '0', '1', '0', '0', '9.3054177E+7' ]

  # valid check
  if (defined $data) {
    my $station_open = $data->{header}->[4] || 99;

    write_file($check_file.".lock", "$station_open#$clerk_id\n");
    #write_file("/opt/pos/server/data/tmp/checksrefresh.txt", "1\n");
  }
  else {
    $error = "Invalid check";
  }

  if (defined $callback) {
    return $callback."(".&encode_json({ error => $error, data => $recs }).");";
  }
  else {
    return &encode_json({ error => $error, data => $recs });
  }

};

# return server version of the mobile binary
get '/mobile_server_version' => sub {
  
  my $check_file = $ENV{POS}."/server/mobile/version.ver";
  my $binary_version = 0;

  if (-e "$check_file") {
    open(F, "< $check_file");
    while (my $line = <F>) {
     $binary_version = $line;
     }
    close F;
  }

  return $binary_version 
};


any '/mobile_parameters'=> sub {
  my $callback = params->{callback};
  my $screen_resolution = params->{screen_resolution};

  my $error = "";
  my $lines = "";
  
  if (! defined $screen_resolution) {
    $error = "Missing 'screen_resolution' parameter";
    return $error;
  }
  else {
    if (exists $MOBILE_CACHE{$screen_resolution}) {
      return $MOBILE_CACHE{$screen_resolution};
    }

    my $file_p = $ENV{POS}.'/common/addons/mobile/'.$screen_resolution.'.ini';

    open(F, "< $file_p") || return("ERRO");
    while (my $line = <F>) {

     $line =~ s/\n//g;
     $lines = $lines."@".$line;
     }
    close F;

    my $file = $ENV{POS}.'/common/bin/db_export.pl --mobile_messages';
    my $array = `$file`; 

    my $resultado = $lines."#".$array;

    #$MOBILE_CACHE{$screen_resolution} = $resultado;

    return $resultado;
  }
};


# return server binary
get '/mobile_update' => sub {
  my $new_binary_file = $ENV{POS}."/server/mobile/Mobile.exe";
  my $buf = undef;

  if (-e "$new_binary_file") {
    open(F, "< $new_binary_file");
    binmode F;
    my($data,$n);
    while  (($n = read F, $data, 1024) != 0) {
      $buf .= $data;
     }
    close F;
  }

  content_type 'application/octet-stream';
  header('Content-Disposition' => 'attachment; filename="Mobile.exe"');

  return $buf;
};

get '/json/quick_handy/data' => sub {
  header 'Access-Control-Allow-Origin' => '*';
  content_type("text/json");
  my $callback = params->{callback};

  my $db = $MACHINE->database();
  my $areas = $MACHINE->areas();

  my %info = ();

  my $recs = $db->select("SELECT id, passwd, login FROM clerks WHERE deleted = 0");

  foreach my $r (@$recs) {
    $info{clerks}->{$r->{passwd}} = $r;
  }

  $recs = $db->select("SELECT id, handy_plu, name, sell_price1 AS sell_price, sell_vat1 AS sell_vat FROM products WHERE deleted = 0 AND handy_plu <> ''");

  foreach my $r (@$recs) {
    $info{products}->{$r->{handy_plu}} = $r;
  }

  foreach my $a (@$areas) {
    #print STDERR "##### ".$a->start_table()."\n";
    #print STDERR "##### ".$a->tables_number()."\n\n";

    for (my $i = $a->start_table(); $i < $a->start_table() + $a->tables_number(); $i++) {
      if ($a->start_table() > 0) {
        $info{tables}->{$i} = $a->number();
      }
      else {
        $info{tables}->{$i+1} = $a->number();
      }
    }
  }

  return to_json(\%info, { utf8 => 0 });
};

sub _random_id {
  my $str = &strftime("%Y%m%d%H%M%S", localtime) . rand();
  $str =~ s/\.//g;
  return $str;
}

sub _date_based_on_closing_hour {
  my $sale_date = shift;
  my $closing   = shift;

  $closing = $closing->[0] if ref($closing);

  my( $y, $m, $d, $h, $mi, $s ) = $sale_date =~ /^(\d+)\-(\d+)\-(\d+) (\d+):(\d+):(\d+)$/;

  my $close = "$y-$m-$d $closing";
  my( $y2, $m2, $d2, $h2, $mi2, $s2 ) = $close =~ /^(\d+)\-(\d+)\-(\d+) (\d+):(\d+):(\d+)$/;

  my $sale_time  = &Date_to_Time( $y, $m, $d, $h, $mi, $s );
  my $close_time = &Date_to_Time( $y2, $m2, $d2, $h2, $mi2, $s2 );

  my $diff = 0;

  $diff = $sale_time - $close_time;

  if ($h2 >= 12) {

    if ($diff > 0) {
      $sale_date =~ s/\s+.*?$//;
      return $sale_date;
      #return UnixDate(DateCalc(ParseDate($sale_date),"+1 dias"), "%Y-%m-%d");
    } else {
      #$sale_date =~ s/\s+.*?$//;
      #return $sale_date;
      return UnixDate(DateCalc(ParseDate($sale_date),"-1 dias"), "%Y-%m-%d");
    }

  } else {

    if ($diff > 0) {
      $sale_date =~ s/\s+.*?$//;
      return $sale_date;
    } else {
      return UnixDate(DateCalc(ParseDate($sale_date),"-1 dias"), "%Y-%m-%d");
    }

  }

  $sale_date =~ s/\s+.*?$//;
  return $sale_date;
}

sub _debug {
  my $m = shift;
  return unless $DEBUG;

  my $d = &strftime("%Y-%m-%d %H:%M:%S", localtime);

  open(F, ">> /opt/pos/mobile.log");
  print F "$d> $m\n";
  close(F);

}

sub _refresh_tables {
  my $machine = shift;

  my $dir_server_tmp = $machine->server_dir_tmp();
  my $dir_registered_stations = $machine->dir_server().'/server/data/registered_stations';

  # tables refresh on server
  write_file("$dir_server_tmp/tables_refresh#0.txt", "1");

  my @stations = read_dir($dir_registered_stations);

  # tables refresh on registered stations
  for (@stations) {
    write_file("$dir_server_tmp/tables_refresh#".$_.".txt", "1");
  }
}

true;

