package Pricing; # # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (c) Rainer Gümpelein, TeilRad GmbH # #Rental counting and pricing methods # #perl -cw #use lib qw(/var/www/copri-bike/shareeapp-operator/src); # use strict; use warnings; use POSIX; use CGI; # only for debugging use Scalar::Util qw(looks_like_number); use DateTime; use DateTime::Format::Pg; use Lib::Config; use Mod::Libenz; use Mod::DBtank; use Mod::Callib; use Mod::Basework; use Data::Dumper; my $cf = new Config; my $lb = new Libenz; my $dbt = new DBtank; my $cal = new Callib; my $bw = new Basework; sub new { my $class = shift; my $self = {}; bless($self,$class); return $self; } my $dbh = ""; sub round(){ my $self = shift; my ($amount) = @_; $amount = $amount * (10**(2)); my $rounded = int($amount + .5 * ($amount<=> 0)) / (10**(2)); return $rounded; } sub only_first_free(){ my $self = shift; my $ctpos = shift; my %varenv = $cf->envonline(); my $pref = { table => "contenttrans", table_pos => "contenttranspos", fetch => "one", ca_id => "=::$ctpos->{ca_id}", ct_id => "=::$ctpos->{ct_id}", c_id => "!=::$ctpos->{c_id}", #txt10 => "IN::('available','canceled')", int10 => "IN::('1','6')", }; $pref = { %$pref, time_range => "cp.start_time >= '$ctpos->{start_time}' and cp.start_time < '$ctpos->{end_time}' and cp.start_time != cp.end_time" }; my $record = { c_id => 0 }; $record = $dbt->collect_post($dbh,$pref); return $record; } #converts clock-time to minutes sub clock_minutes { my $self = shift; my $clockfloat = shift;# like 12:35 my $day = 0; my $hour = 0; my $min = 0; ($hour,$min) = split(/:/, $clockfloat); ($day,$hour) = split(/\sday\s/, $hour) if($hour =~ /\sday\s/); $hour += 24 * $day if($day && $day > 0); my $minutes = $min; $minutes = ($hour * 60) + $min if($hour && $hour > 0); return $minutes; } #new counting rental time in hours method (last sharee_pricing) sub counting_rental { my $self = shift; my $varenv = shift; my $ctpos = shift; my $todo = shift; my $today4db = strftime("%Y-%m-%d %H:%M:%S",localtime(time)); my $return = {}; my $counting = { c_id => $ctpos->{c_id} }; my $computed_end_time = $ctpos->{end_time} || $today4db; $computed_end_time = $today4db if($ctpos->{int10} && $ctpos->{int10} == 3); my $computed_start_time = $ctpos->{start_time} || $today4db;#should be only used on parts #main counting rental time and convert it to minute my $dt0 = DateTime::Format::Pg->parse_datetime($computed_start_time); my $dt1 = DateTime::Format::Pg->parse_datetime($computed_end_time); my $dur10 = $dt1->subtract_datetime($dt0); my ($durdd,$durhh,$durmm) = $dur10->in_units( 'days', 'hours','minutes' ); $durhh = sprintf('%.2d',$durhh); $durmm = sprintf('%.2d',$durmm); my $real_clock = "$durhh:$durmm"; $real_clock = "$durdd day $durhh:$durmm" if($durdd); my $computed_clock = $real_clock; my $rental_minute = $self->clock_minutes($computed_clock); #if end_station == start_station and rental minutes < 5 minutes, then 0 $rental_minute = 0 if($ctpos->{int04} == $ctpos->{int06} && $rental_minute && $rental_minute < 5); my $rental_minute_all = $rental_minute; #init with some defaults my $total_price = 0; my $ctpos_freed = { c_id => 0 }; #convert tariff unit by minute time my $tariff_unitbyminute = 60;#defaults to Stundentarif if($ctpos->{time01} && $ctpos->{time01} =~ /[1-9]/){ $tariff_unitbyminute = $self->clock_minutes($ctpos->{time01}); } #substract free time from rental time ex. 00:30 Min/Gratis my $freed_time = ""; if($ctpos->{time02} && $ctpos->{time02} =~ /[1-9]/){ $ctpos_freed = $self->only_first_free($ctpos); if(!$ctpos_freed->{c_id}){ my ($dhh,$dmm) = split(/:/,$ctpos->{time02}); $freed_time = "- $dhh:$dmm" if($dhh || $dmm); #adding free minutes to start_time $dt0->add( hours => $dhh, minutes => $dmm ) if(looks_like_number($dhh) && looks_like_number($dmm)); my $cdur10 = $dt1->subtract_datetime($dt0); my ($durdd,$durhh,$durmm) = $cdur10->in_units( 'days', 'hours','minutes' ); $durhh = sprintf('%.2d',$durhh); $durmm = sprintf('%.2d',$durmm); $computed_clock = "$durhh:$durmm"; $computed_clock = "$durdd day $durhh:$durmm" if($durdd); $rental_minute = $self->clock_minutes($computed_clock); #print "$computed_clock|$rental_minute"; } } my $rental_unit = 0; my $price_by_allunit = 0; my $max_daily_unit = 0;#how many rental_minute is one daily_unit my $max_fee_daily_minute = 0; my $rental_day_price = 0; my $restal_minute = 0; my $rental_time_price = 0; my $rental_unit_rounded = 0; my $used_max_fee = "off"; my $used_methode = ""; #on time unit (int35 keeps unit_price1) if($rental_minute && $rental_minute > 0 && $ctpos->{int35} && $ctpos->{int35} > 0){ $rental_unit = $rental_minute / $tariff_unitbyminute; $price_by_allunit = $rental_unit * $ctpos->{int35}; #max_fee/day (day = 1440 minutes) #useless if time01 unit is set to 24:00 if($ctpos->{int17} && $ctpos->{int17} > 0 && $price_by_allunit >= $ctpos->{int17} && $tariff_unitbyminute < 1440){ $used_methode .= "max_fee/day"; $used_max_fee = "ON $ctpos->{int17}"; $max_daily_unit = $ctpos->{int17} / $ctpos->{int35}; $max_fee_daily_minute = $max_daily_unit * $tariff_unitbyminute; my $days_dec = $rental_minute / 1440; my $days = $days_dec; ($days,my $ddec) = split(/\./, $days_dec) if($days_dec =~ /\.\d/); if($days > 0){ $rental_day_price = $days * $ctpos->{int17}; $counting->{int39} = $days;#by day $used_methode .= " | rental_day_price: $days days * $ctpos->{int17} pos.int17"; } $restal_minute = $rental_minute - $days * 1440; if($restal_minute >= $max_fee_daily_minute){ $rental_day_price += $ctpos->{int17}; $counting->{int39} += 1;#by day $used_methode .= " | += $ctpos->{int17} pos.int17"; }else{ $rental_unit = $restal_minute / $tariff_unitbyminute; # $rental_unit = sprintf('%.2f', $rental_unit); $rental_unit_rounded = $rental_unit; if($rental_unit =~ /(\d+)\.(\d+)$/){ $rental_unit_rounded = $1 + 1 if($2 > 0); } $rental_time_price = $rental_unit_rounded * $ctpos->{int35}; $counting->{int38} = $rental_unit_rounded;#by time $used_methode .= " | $rental_unit_rounded rental_unit_rounded * $ctpos->{int35} int35"; } #else if price by all unit < max_fee/day }else{ $rental_unit = $rental_minute / $tariff_unitbyminute; $rental_unit = sprintf('%.2f', $rental_unit); $rental_unit_rounded = $rental_unit; if($rental_unit =~ /(\d+)\.(\d+)/){ $rental_unit_rounded = $1 + 1 if($2 > 0); } if($ctpos->{int36} == 0 && $ctpos->{int17} && $ctpos->{int17} > 0){ $counting->{int38} = $rental_unit_rounded;#count by time $rental_time_price = $rental_unit_rounded * $ctpos->{int35}; $used_methode .= " | $rental_unit_rounded rental_unit_rounded * $ctpos->{int35} int35"; }elsif($ctpos->{int36} > 0){#if second unit_price2 defined $counting->{int40} = 1;#by day $rental_time_price = 1 * $ctpos->{int35}; $used_methode .= " | 1 rental_unit_rounded * $ctpos->{int35} int35 with unit_price2 $ctpos->{int36} 36"; if($rental_unit_rounded >= 2 && $ctpos->{int36} && $ctpos->{int36} > 0){ my $rental_unit_rounded2 = $rental_unit_rounded - 1;#by day $counting->{int41} = $rental_unit_rounded2; $rental_time_price += $rental_unit_rounded2 * $ctpos->{int36}; $used_methode .= " | $rental_unit_rounded2 rental_unit_rounded * $ctpos->{int36} int36"; } } } } my $computed_hours = $rental_minute / 60; $total_price = $rental_day_price + $rental_time_price; $used_methode .= " --> $total_price total_price = $rental_day_price rental_day_price + $rental_time_price rental_time_price"; my $discount = ""; my $discount_val = $ctpos->{int07} || 0; if($discount_val != 0 && $total_price){ my $discount_eur = $discount_val; $discount_eur = $total_price * $discount_val/100 if(!$ctpos->{int08} || $ctpos->{int08} != 1); $total_price -= $discount_eur; $discount = "-" . $ctpos->{int07}; if($ctpos->{int08} && $ctpos->{int08} == 1){ $discount .= " €"; }else{ $discount =~ s/\.00//; $discount .= "%"; } } $total_price = sprintf('%.2f', $total_price); $total_price = $total_price * $ctpos->{int01} if($ctpos->{int01}); $return->{start_time} = "$computed_start_time"; $return->{end_time} = "$computed_end_time"; $return->{freed_time} = "$freed_time"; $return->{computed_hours} = "$computed_hours"; $return->{unit_price} = "$ctpos->{int35}"; $return->{real_clock} = "$real_clock"; $return->{total_price} = "$total_price"; $return->{discount} = "$discount"; $return->{rentalog}->{real_clock} = "$real_clock"; $return->{rentalog}->{freed_time} = "$freed_time"; $return->{rentalog}->{computed_clock} = "$computed_clock"; $return->{rentalog}->{computed_hours} = "$computed_hours"; $return->{rentalog}->{rental_minute} = "$rental_minute"; $return->{rentalog}->{rental_minute_all} = "$rental_minute_all"; $return->{rentalog}->{max_fee_daily_minute} = "$max_fee_daily_minute"; $return->{rentalog}->{tariff_unitbyminute} = "$tariff_unitbyminute"; $return->{rentalog}->{restal_minute} = "$restal_minute"; $return->{rentalog}->{rental_unit_rounded} = "$rental_unit_rounded"; $return->{rentalog}->{rental_unit} = "$rental_unit"; $return->{rentalog}->{price_by_allunit} = "$price_by_allunit"; $return->{rentalog}->{rental_day_price} = "$rental_day_price"; $return->{rentalog}->{total_price} = "$total_price"; $return->{rentalog}->{rental_time_price} = "$rental_time_price"; $return->{rentalog}->{ctpos_freed} = $ctpos_freed->{c_id}; $return->{rentalog}->{used_max_fee} = "$used_max_fee"; $return->{rentalog}->{used_methode} = "$used_methode"; $return->{rentalog}->{counting} = $counting; #$bw->log("Pricing counting_rental return:",$return,""); return ($return,$counting); }#end counting_rental #all other values returned by user_bikes_occupied sub fetch_rentalfeed { my $self = shift; my $varenv = shift; my $ctpos = shift; my $returned_counting = shift || {}; my $lang = "de"; my $td_template = $dbt->rental_description_template(); my $bike_group = "$dbt->{operator}->{$varenv->{dbname}}->{oprefix}$ctpos->{int29}" || ""; my $return = {}; $return->{bike_group} = ["$bike_group"]; #TOD save with prefix $return->{station} = "$dbt->{operator}->{$varenv->{dbname}}->{oprefix}$ctpos->{int04}"; $return->{uri_operator} = "$varenv->{wwwhost}";#TOD, should be DB select $return->{bike} = "$dbt->{operator}->{$varenv->{dbname}}->{oprefix}$ctpos->{barcode}"; #TOD save also sig prefix if($dbt->{operator}->{$varenv->{dbname}}->{oprefix} eq "SX"){ $return->{bike} = "S3X$ctpos->{barcode}"; #$return->{station} = "S3X$ctpos->{int04}"; } $return->{state} = "$dbt->{copri_conf}->{bike_state}->{$ctpos->{int10}}" || ""; $return->{lock_state} = "$dbt->{copri_conf}->{lock_state}->{$ctpos->{int20}}"; #defaults $return->{bike_type}->{category} = "city"; $return->{bike_type}->{wheels} = "2"; #for station_type_id mapping if($ctpos->{int29} && $ctpos->{int29} == 300101){ $return->{bike_type}->{category} = "cargo"; $return->{bike_type}->{wheels} = "2"; $return->{bike_type}->{wheels} = "3" if($ctpos->{txt01} =~ /drei|trike/i); if($ctpos->{txt01} =~ /E-/i){ $return->{bike_type}->{engine}->{manufacturer} = "dummy"; my $max_bars = 5; my $current_bars = 0; $return->{bike_type}->{battery}->{charge_max_bars} = "$max_bars"; $return->{bike_type}->{battery}->{charge_current_bars} = "$current_bars"; $return->{bike_type}->{battery}->{charge_current_percent} = "0"; $return->{bike_type}->{battery}->{hidden} = "0";#1=hide charge view if($ctpos->{int19}){ $current_bars = $bw->battery_bars($max_bars,$ctpos->{int19}); $return->{bike_type}->{battery}->{charge_current_bars} = "$current_bars"; $return->{bike_type}->{battery}->{charge_current_percent} = "$ctpos->{int19}"; } } } $return->{Ilockit_ID} = "$ctpos->{txt18}" if($ctpos->{int11} == 2); $return->{description} = "$ctpos->{txt01}"; $return->{request_time} = "$ctpos->{itime}"; $return->{system} = "Ilockit" if($ctpos->{int11} && $ctpos->{int11} == 2); $return->{system} = "sigo" if($ctpos->{int11} && $ctpos->{int11} == 3); if($ctpos->{int11}){ ($return->{gps}->{latitude},$return->{gps}->{longitude}) = split(/,/,$ctpos->{txt06}); #deprecated $return->{tariff_description}->{name} = "$ctpos->{txt04}"; $return->{tariff_description}->{number} = "$ctpos->{int09}"; $return->{tariff_description}->{eur_per_hour} = "$ctpos->{int02}" || "0"; $return->{tariff_description}->{max_eur_per_day} = "$ctpos->{int17}" || "0"; $return->{tariff_description}->{free_hours} = "$ctpos->{int16}" if($ctpos->{int16} && $ctpos->{int16} > 0); $return->{tariff_description}->{abo_eur_per_month} = "$ctpos->{int15}" if($ctpos->{int15} && $ctpos->{int15} > 0); #new rental_description $return->{rental_description}->{name} = "$ctpos->{txt04}"; $return->{rental_description}->{id} = "$ctpos->{int09}"; $return->{rental_description}->{reserve_timerange} = "15"; $return->{rental_description}->{reserve_timerange} = "30" if($ctpos->{int11} == 3); foreach my $td (sort keys (%$td_template)){ my $time_unit = ""; if($td_template->{$td}->{int35} && $ctpos->{int35} && $ctpos->{int35} > 0){ $ctpos->{int35} =~ s/\./,/ if($lang eq "de"); $time_unit = $dbt->time_format($ctpos->{time01}); $return->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{int35}","$ctpos->{int35} € / $time_unit"]; }elsif($td_template->{$td}->{int36} && $ctpos->{int36} && $ctpos->{int36} > 0){ $ctpos->{int36} =~ s/\./,/ if($lang eq "de"); $time_unit = $dbt->time_format($ctpos->{time01}); $return->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{int36}", "$ctpos->{int36} € / $time_unit"]; }elsif($td_template->{$td}->{int17} && $ctpos->{int17} && $ctpos->{int17} > 0){ $ctpos->{int17} =~ s/\./,/ if($lang eq "de"); $return->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{int17}","$ctpos->{int17} € / Tag"]; }elsif($td_template->{$td}->{time02} && $ctpos->{time02} =~ /[1-9]/){ $time_unit = $dbt->time_format($ctpos->{time02}); $return->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{time02}","$time_unit / Tag"]; }elsif($td_template->{$td}->{xduration} && $returned_counting->{real_clock} && $returned_counting->{real_clock} =~ /[1-9]/){ $time_unit = $dbt->time_format($returned_counting->{real_clock}); $return->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{xduration}","$time_unit"]; }elsif($td_template->{$td}->{xprice} && $returned_counting->{total_price} && $returned_counting->{total_price} > 0){ $returned_counting->{total_price} =~ s/\./,/ if($lang eq "de"); $return->{rental_description}->{tarif_elements}->{$td} = ["$td_template->{$td}->{xprice}","$returned_counting->{total_price} €"]; } }#end new rental_description } return $return; } #CO2 calculator #Bsp Berechnungen: # Pkw: # Distanz * CO2-Emission Pkw / 100 km # 8.760 km * 20 kg CO2 / 100 km = 1.752 kg CO2 # Pedelec: # Distanz * CO2-Emission Pedelec / 100 km # 10.950 km * 0,546 kg CO2 / 100 km = 62 kg CO2 # # Aus der Differenz zwischen der CO2-Emission Pkw und der CO2-Emission Pedelec ergibt sich das Einsparpotenzial: # 1.752 kg CO2 – 62 kg CO2 = 1.690 kg CO2, also rund 1,7 t CO2 pro Jahr # sub co2calc { my $self = shift; my $ctpos = shift; my $co2diff = 0; my $co2pkw = $ctpos->{int26} * 20 / 100; my $co2ped = $ctpos->{int26} * 0.546 / 100; $co2diff = $co2pkw - $co2ped; $co2diff = sprintf('%.2f',$co2diff); $co2diff =~ s/\./,/; return $co2diff; } #calculates sprit saving #disabled sub sprit2calc { my $self = shift; my $ctpos = shift; my $einzel = $ctpos->{int02}; my $menge = $ctpos->{int03}; my $discount_val = $ctpos->{int07} || 0; my $total = 0; if($discount_val != 0 && $einzel && $menge){ my $discount_eur = $discount_val; #if int08 != 1 alias € $discount_eur = $einzel * $menge * $discount_val/100 if($ctpos->{int08} != 1); $total = $einzel * $menge - $discount_eur; }elsif($einzel && $menge){ $total = $einzel * $menge; } my $sprit_price = 0; $sprit_price = $ctpos->{int26} * 0.3 if($ctpos->{int26} != 0); $sprit_price -= $total; $sprit_price = sprintf('%.2f',$sprit_price); $sprit_price =~ s/\./,/; $sprit_price = ""; return $sprit_price; } #computes article position price and rabatt sub price2calc { my $self = shift; my $ctpos = shift; my $total = 0; my $discount = ""; my $einzel = $ctpos->{int02} || 0; my $menge = $ctpos->{int03} || 0; my $discount_val = $ctpos->{int07} || 0; if($discount_val != 0 && $einzel && $menge){ my $discount_eur = $discount_val; #if int08 != 1 alias € $discount_eur = $einzel * $menge * $discount_val/100 if(!$ctpos->{int08} || $ctpos->{int08} != 1); $total = $einzel * $menge - $discount_eur; }elsif($einzel && $menge){ $total = $einzel * $menge; } $total = $total * $ctpos->{int01} if($ctpos->{int01}); if($ctpos->{int07} && $ctpos->{int07} > 0 && $menge > 0){ $discount = "-" . $ctpos->{int07}; if($ctpos->{int08} && $ctpos->{int08} == 1){ $discount .= " €"; }else{ $discount =~ s/\.00//; $discount .= " %"; } } return ($total,$discount); } #computes operator invoices for accounting sub operator_accounting2calc { my $self = shift; my $varenv = shift; my $ctpos = shift;#client invoices my $ctf = shift;#Operator-Faktura config my $tplf = $dbt->get_tpl($dbh,"196");#Operator-Faktura my @tplf_order = split /,/,$tplf->{tpl_order}; #returned values my $oac = { int01 => 0, int02 => 0, int93 => 0, int94 => 0, int95 => 0, int96 => 0, int97 => 0, int98 => 0, int99 => 0, int100 => 0, }; foreach(@tplf_order){ my ($key,$val,$size) = split /=/,$_; if($key =~ /int/){ $oac->{int99} = $ctpos->{int01} if($key eq "int01" && $ctpos->{int01});#invoice capture brutto $oac->{int01} = $ctpos->{int01} / 119 * 100 if($key eq "int01" && $ctpos->{int01});#invoice capture netto if($ctpos->{state} =~ /Lastschrift/){ $oac->{int93} = $oac->{int01} / 100 * $ctf->{$key} if($key eq "int01");#7,5% $oac->{int93} = sprintf('%.3f', $oac->{int93}); $oac->{int98} = $oac->{int93} * 0.19 if($oac->{int93});#UmSt auf 7,5% $oac->{int98} = sprintf('%.3f', $oac->{int98}); $oac->{int94} = $oac->{int01} / 100 * $ctf->{$key} * -1 if($key eq "int02");#po Disagio % $oac->{int94} = sprintf('%.3f', $oac->{int94}); $oac->{int95} = $ctf->{$key} * -1 if($key eq "int04");#po Trans $oac->{int95} = sprintf('%.3f', $oac->{int95}); $oac->{int96} = $ctf->{$key} * -1 if($key eq "int06");#po Zahlungsmeldung $oac->{int96} = sprintf('%.3f', $oac->{int96}); $oac->{int97} = 0; } if($ctpos->{state} =~ /Kreditkarte/){ $oac->{int93} = $oac->{int01} / 100 * $ctf->{$key} if($key eq "int01");#7,5% $oac->{int93} = sprintf('%.3f', $oac->{int93}); $oac->{int98} = $oac->{int93} * 0.19 if($oac->{int93});#UmSt auf 7,5% $oac->{int98} = sprintf('%.3f', $oac->{int98}); $oac->{int94} = $oac->{int01} / 100 * $ctf->{$key} * -1 if($key eq "int03");#po Disagio % $oac->{int94} = sprintf('%.3f', $oac->{int94}); $oac->{int95} = $ctf->{$key} * -1 if($key eq "int05");#po Trans $oac->{int95} = sprintf('%.3f', $oac->{int95}); $oac->{int96} = $ctf->{$key} * -1 if($key eq "int06");#po Zahlungsmeldung $oac->{int96} = sprintf('%.3f', $oac->{int96}); $oac->{int97} = $ctf->{$key} * -1 if($key eq "int07");#po CC Zuordnung $oac->{int97} = sprintf('%.3f', $oac->{int97}); } #operator accounting $oac->{int02} = $oac->{int01} + $oac->{int94} + $oac->{int95} + $oac->{int96} + $oac->{int97}; $oac->{int02} = sprintf('%.3f', $oac->{int02}); #operator invoice $oac->{int100} = $oac->{int93} + $oac->{int98}; $oac->{int100} = sprintf('%.3f', $oac->{int100}); } } return $oac; } 1;