diff --git a/installer/data.sql b/installer/data.sql index ed3fa723..0d59dbba 100644 --- a/installer/data.sql +++ b/installer/data.sql @@ -1,8 +1,9 @@ --- MariaDB dump 10.19 Distrib 10.5.19-MariaDB, for debian-linux-gnu (x86_64) +/*M!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.5.29-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: libki-mariadb Database: libki -- ------------------------------------------------------ --- Server version 10.3.5-MariaDB-10.3.5+maria~jessie +-- Server version 12.1.2-MariaDB-ubu2404 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -167,8 +168,8 @@ INSERT INTO `settings` VALUES ('','DefaultGuestSessionTimeAllowance','45'); INSERT INTO `settings` VALUES ('','DefaultGuestTimeAllowance','45'); INSERT INTO `settings` VALUES ('','DefaultSessionTimeAllowance','45'); INSERT INTO `settings` VALUES ('','DefaultTimeAllowance','45'); -INSERT INTO `settings` VALUES ('','DisplayReservationStatusWithin','60'); INSERT INTO `settings` VALUES ('','DisplayPublicReservationsPage','enabled'); +INSERT INTO `settings` VALUES ('','DisplayReservationStatusWithin','60'); INSERT INTO `settings` VALUES ('','GuestBatchCount','40'); INSERT INTO `settings` VALUES ('','MinimumReservationMinutes','5'); INSERT INTO `settings` VALUES ('','PostCrashTimeout','5'); @@ -177,9 +178,8 @@ INSERT INTO `settings` VALUES ('','ReservationShowUsername','RSD'); INSERT INTO `settings` VALUES ('','ReservationTimeout','15'); INSERT INTO `settings` VALUES ('','ShowFirstLastNames','1'); INSERT INTO `settings` VALUES ('','ThirdPartyURL',''); -INSERT INTO `settings` VALUES ('','TimeDisplayFormat','24'); INSERT INTO `settings` VALUES ('','UserCategories',''); -INSERT INTO `settings` VALUES ('','Version','5.2.1'); +INSERT INTO `settings` VALUES ('','Version','5.2.x'); /*!40000 ALTER TABLE `settings` ENABLE KEYS */; UNLOCK TABLES; @@ -192,6 +192,15 @@ LOCK TABLES `statistics` WRITE; /*!40000 ALTER TABLE `statistics` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Dumping data for table `transactions` +-- + +LOCK TABLES `transactions` WRITE; +/*!40000 ALTER TABLE `transactions` DISABLE KEYS */; +/*!40000 ALTER TABLE `transactions` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Dumping data for table `user_roles` -- @@ -218,4 +227,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2023-07-17 7:52:06 +-- Dump completed on 2026-02-11 10:59:54 diff --git a/installer/schema.sql b/installer/schema.sql index 01c0ce3a..bdbd15c8 100644 --- a/installer/schema.sql +++ b/installer/schema.sql @@ -1,8 +1,9 @@ --- MariaDB dump 10.19 Distrib 10.11.13-MariaDB, for debian-linux-gnu (x86_64) +/*M!999999\- enable the sandbox mode */ +-- MariaDB dump 10.19 Distrib 10.5.29-MariaDB, for debian-linux-gnu (x86_64) -- --- Host: localhost Database: libki +-- Host: libki-mariadb Database: libki -- ------------------------------------------------------ --- Server version 11.8.5-MariaDB-ubu2404 +-- Server version 12.1.2-MariaDB-ubu2404 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -374,7 +375,7 @@ CREATE TABLE `transactions` ( KEY `idx_instance_status` (`instance`,`status`), KEY `idx_created_on` (`created_on`), CONSTRAINT `user_transactions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -418,6 +419,7 @@ CREATE TABLE `users` ( `lastname` varchar(191) DEFAULT '', `category` varchar(191) DEFAULT '', `creation_source` varchar(191) DEFAULT NULL, + `gratis_print_balance` int(11) NOT NULL DEFAULT 0 COMMENT 'Number of free prints remaining; represents pages or cents depending on system settings', PRIMARY KEY (`id`), UNIQUE KEY `unique_username` (`instance`,`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; @@ -432,4 +434,4 @@ CREATE TABLE `users` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2025-12-19 13:32:36 +-- Dump completed on 2026-02-11 10:59:54 diff --git a/installer/versions/5/2.x_gratis_print_balance.sql b/installer/versions/5/2.x_gratis_print_balance.sql new file mode 100644 index 00000000..223a09e0 --- /dev/null +++ b/installer/versions/5/2.x_gratis_print_balance.sql @@ -0,0 +1,3 @@ +ALTER TABLE `users` + ADD COLUMN `gratis_print_balance` INT(11) NOT NULL DEFAULT 0 + COMMENT 'Number of free prints remaining; represents pages or cents depending on system settings'; diff --git a/lib/Libki/Controller/API/PrintStation/v1_0.pm b/lib/Libki/Controller/API/PrintStation/v1_0.pm index e0eabc39..dcaa7df8 100755 --- a/lib/Libki/Controller/API/PrintStation/v1_0.pm +++ b/lib/Libki/Controller/API/PrintStation/v1_0.pm @@ -134,20 +134,22 @@ sub print_jobs : Path('print_jobs') : Args(0) { my $print_jobs = []; while ( my $j = $jobs->next ) { - my $cost = Libki::Utils::Printing::calculate_job_cost( $c, { print_job => $j } ); + my ( $cost, $gratis_discount ) = Libki::Utils::Printing::calculate_job_cost( $c, { print_job => $j } ); push( @$print_jobs, { - print_job_id => $j->id, - printer => $j->printer, - copies => $j->copies, - created_on => $c->format_dt( { dt => $j->created_on, include_time => 1 } ), - print_file_id => $j->print_file_id, - pages => $j->print_file->pages, - cost => $cost, + print_job_id => $j->id, + printer => $j->printer, + copies => $j->copies, + created_on => $c->format_dt( { dt => $j->created_on, include_time => 1 } ), + print_file_id => $j->print_file_id, + pages => $j->print_file->pages, + cost => $cost, + gratis_discount => $gratis_discount, } ); + } my $data = { diff --git a/lib/Libki/Controller/Administration.pm b/lib/Libki/Controller/Administration.pm index bff52b1c..198142f5 100644 --- a/lib/Libki/Controller/Administration.pm +++ b/lib/Libki/Controller/Administration.pm @@ -51,6 +51,7 @@ sub index : Path : Args(0) { PrinterConfiguration => $c->setting('PrinterConfiguration'), ShowFirstLastNames => $c->setting('ShowFirstLastNames'), UserCategories => $c->setting('UserCategories'), + GratisPrintingValue => $c->setting('GratisPrintingValue'), locations => \@locations, types => \@types, ); diff --git a/lib/Libki/Controller/Administration/API/DataTables.pm b/lib/Libki/Controller/Administration/API/DataTables.pm index df086c85..4b9f2383 100644 --- a/lib/Libki/Controller/Administration/API/DataTables.pm +++ b/lib/Libki/Controller/Administration/API/DataTables.pm @@ -34,7 +34,7 @@ sub users : Local Args(0) { my $params = $c->req->params; # We need to map the table columns to field names for ordering - my @columns = qw/me.username me.lastname me.firstname me.category allotments.minutes session.minutes me.status me.notes me.is_troublemaker client.name session.status me.creation_source/; + my @columns = qw/me.username me.lastname me.firstname me.category allotments.minutes session.minutes me.funds me.gratis_print_balance me.status me.notes me.is_troublemaker client.name session.status me.creation_source/; my $search_term = $params->{"search[value]"}; my $filter; @@ -127,8 +127,9 @@ sub users : Local Args(0) { category => $u->category, client_name => defined( $u->session ) ? $u->session->client->name : undef, creation_source => $u->creation_source, - firstname => $u->firstname, - funds => $u->funds, + firstname => $u->firstname, + funds => $u->funds, + gratis_print_balance => $u->gratis_print_balance, is_troublemaker => $u->is_troublemaker, lastname => $u->lastname, minutes => $u->session ? $u->session->minutes : undef, diff --git a/lib/Libki/Controller/Administration/API/User.pm b/lib/Libki/Controller/Administration/API/User.pm index 85a79a83..64230b7b 100644 --- a/lib/Libki/Controller/Administration/API/User.pm +++ b/lib/Libki/Controller/Administration/API/User.pm @@ -42,17 +42,18 @@ sub get : Local : Args(1) { $c->stash( { - id => $user->id, - username => $user->username, - firstname => $user->firstname, - lastname => $user->lastname, - category => $user->category, - minutes => $user->minutes($c), - status => $user->status, - notes => $user->notes, - funds => $user->funds, - is_troublemaker => $user->is_troublemaker, - troublemaker_until => defined( $user->troublemaker_until ) + id => $user->id, + username => $user->username, + firstname => $user->firstname, + lastname => $user->lastname, + category => $user->category, + minutes => $user->minutes($c), + status => $user->status, + notes => $user->notes, + funds => $user->funds, + gratis_print_balance => $user->gratis_print_balance, + is_troublemaker => $user->is_troublemaker, + troublemaker_until => defined( $user->troublemaker_until ) ? $user->troublemaker_until->strftime('%Y-%m-%d 23:59') : undef, roles => \@roles, @@ -220,15 +221,16 @@ sub update : Local : Args(0) { my $success = 0; - my $id = $c->request->params->{'id'}; - my $firstname = $c->request->params->{firstname}; - my $lastname = $c->request->params->{lastname}; - my $category = $c->request->params->{category}; - my $minutes = $c->request->params->{'minutes'}; - my $funds = $c->request->params->{'funds'}; - my $notes = $c->request->params->{'notes'}; - my $status = $c->request->params->{'status'}; - my @roles = $c->request->params->{'roles'} || []; + my $id = $c->request->params->{'id'}; + my $firstname = $c->request->params->{firstname}; + my $lastname = $c->request->params->{lastname}; + my $category = $c->request->params->{category}; + my $minutes = $c->request->params->{'minutes'}; + my $funds = $c->request->params->{'funds'}; + my $notes = $c->request->params->{'notes'}; + my $gratis_print_balance = $c->request->params->{'gratis_print_balance'} || 0; + my $status = $c->request->params->{'status'}; + my @roles = $c->request->params->{'roles'} || []; # For some reason the list of checkboxes are created # as a list within a list if multiple are checked @@ -247,12 +249,13 @@ sub update : Local : Args(0) { $success = 1 if $user->update( { - firstname => $firstname, - lastname => $lastname, - category => $category, - notes => $notes, - status => $status, - updated_on => $now, + firstname => $firstname, + lastname => $lastname, + category => $category, + gratis_print_balance => $gratis_print_balance, + notes => $notes, + status => $status, + updated_on => $now, } ); diff --git a/lib/Libki/Controller/Public/API/DataTables.pm b/lib/Libki/Controller/Public/API/DataTables.pm index 64d45c84..83e685e0 100644 --- a/lib/Libki/Controller/Public/API/DataTables.pm +++ b/lib/Libki/Controller/Public/API/DataTables.pm @@ -112,12 +112,13 @@ sub prints : Local Args(0) { foreach my $print_job (@prints) { my $print_file = $print_job->print_file; - my $total_cost = Libki::Utils::Printing::calculate_job_cost( + my ($total_cost, $gratis_discount) = Libki::Utils::Printing::calculate_job_cost( $c, { print_job => $print_job, print_file => $print_file, printer => $printers->{printers}->{ $print_job->printer }, + user => $user, } ); @@ -130,7 +131,7 @@ sub prints : Local Args(0) { } keys %{$printers->{printers}} ) { my $printer = $printers->{printers}->{ $key }; - my $total_cost = Libki::Utils::Printing::calculate_job_cost( + my ($total_cost, $gratis_discount) = Libki::Utils::Printing::calculate_job_cost( $c, { print_job => $print_job, @@ -143,6 +144,7 @@ sub prints : Local Args(0) { selected => $key eq $print_job->printer ? 1 : 0, name => $printer->{public_printer_name}, cost => $total_cost, + gratis_discount => $gratis_discount, }; push( @printer_costs, $data ); diff --git a/lib/Libki/Controller/Public/API/User.pm b/lib/Libki/Controller/Public/API/User.pm index c33a78f2..6cee6d2a 100644 --- a/lib/Libki/Controller/Public/API/User.pm +++ b/lib/Libki/Controller/Public/API/User.pm @@ -41,7 +41,8 @@ sub funds : Local Args(0) { delete $c->stash->{$_} for keys %{$c->stash}; $c->stash( { - funds => $user->funds + funds => $user->funds, + gratis_print_balance => $user->gratis_print_balance, } ); delete $c->stash->{Settings}; diff --git a/lib/Libki/Schema/DB/Result/Job.pm b/lib/Libki/Schema/DB/Result/Job.pm index 3d6829b0..1d2670f0 100644 --- a/lib/Libki/Schema/DB/Result/Job.pm +++ b/lib/Libki/Schema/DB/Result/Job.pm @@ -70,7 +70,7 @@ __PACKAGE__->table("jobs"); =head2 data - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 1 =head2 taken @@ -110,7 +110,7 @@ __PACKAGE__->add_columns( "type", { data_type => "varchar", is_nullable => 0, size => 191 }, "data", - { data_type => "longtext", is_nullable => 1 }, + { data_type => "mediumtext", is_nullable => 1 }, "taken", { data_type => "varchar", is_nullable => 1, size => 191 }, "status", @@ -149,8 +149,8 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-04-27 09:23:45 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:D2SKTWMAT1jwHSux1UE7qQ +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:slA0TWq4fcyY7kqYTEuk9g __PACKAGE__->load_components('InflateColumn::Serializer'); __PACKAGE__->add_columns( diff --git a/lib/Libki/Schema/DB/Result/LoginSession.pm b/lib/Libki/Schema/DB/Result/LoginSession.pm index 238c7bce..ac5af6c9 100644 --- a/lib/Libki/Schema/DB/Result/LoginSession.pm +++ b/lib/Libki/Schema/DB/Result/LoginSession.pm @@ -57,7 +57,7 @@ __PACKAGE__->table("login_sessions"); =head2 session_data - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 1 =head2 expires @@ -71,7 +71,7 @@ __PACKAGE__->add_columns( "id", { data_type => "char", is_nullable => 0, size => 72 }, "session_data", - { data_type => "longtext", is_nullable => 1 }, + { data_type => "mediumtext", is_nullable => 1 }, "expires", { data_type => "integer", is_nullable => 1 }, ); @@ -89,8 +89,8 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-04-27 09:23:45 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:m+OJsvQRg7FrVcbnVJwBxg +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:6VF4JGAZI2UkY4knbkR/XQ # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/lib/Libki/Schema/DB/Result/Message.pm b/lib/Libki/Schema/DB/Result/Message.pm index f68f0169..d8f906c7 100644 --- a/lib/Libki/Schema/DB/Result/Message.pm +++ b/lib/Libki/Schema/DB/Result/Message.pm @@ -70,7 +70,7 @@ __PACKAGE__->table("messages"); =head2 content - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 0 =cut @@ -83,7 +83,7 @@ __PACKAGE__->add_columns( "user_id", { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, "content", - { data_type => "longtext", is_nullable => 0 }, + { data_type => "mediumtext", is_nullable => 0 }, ); =head1 PRIMARY KEY @@ -116,8 +116,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-04-27 09:23:45 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hn49kjZD6Hzpl6FiOXBHww +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CuV5c2ONcc5nZazLSr+QhQ # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/lib/Libki/Schema/DB/Result/PrintFile.pm b/lib/Libki/Schema/DB/Result/PrintFile.pm index 1e17122c..812f7e69 100644 --- a/lib/Libki/Schema/DB/Result/PrintFile.pm +++ b/lib/Libki/Schema/DB/Result/PrintFile.pm @@ -64,7 +64,7 @@ __PACKAGE__->table("print_files"); =head2 filename - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 0 =head2 content_type @@ -141,7 +141,7 @@ __PACKAGE__->add_columns( "id", { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, "filename", - { data_type => "longtext", is_nullable => 0 }, + { data_type => "mediumtext", is_nullable => 0 }, "content_type", { data_type => "varchar", is_nullable => 1, size => 191 }, "data", @@ -246,8 +246,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07051 @ 2023-07-11 07:35:11 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9IRAjpODFDD7mIKVnM/Jgg +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:kc5pJ/hX1R5YWDHG53PgHQ # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/lib/Libki/Schema/DB/Result/PrintJob.pm b/lib/Libki/Schema/DB/Result/PrintJob.pm index bfbb10d8..6d8a32a4 100644 --- a/lib/Libki/Schema/DB/Result/PrintJob.pm +++ b/lib/Libki/Schema/DB/Result/PrintJob.pm @@ -81,7 +81,7 @@ __PACKAGE__->table("print_jobs"); =head2 data - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 1 =head2 printer @@ -148,7 +148,7 @@ __PACKAGE__->add_columns( "copies", { data_type => "integer", is_nullable => 1 }, "data", - { data_type => "longtext", is_nullable => 1 }, + { data_type => "mediumtext", is_nullable => 1 }, "printer", { data_type => "varchar", is_nullable => 0, size => 191 }, "user_id", @@ -240,8 +240,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07051 @ 2023-07-11 07:35:11 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:09Hi0qsW0zShp8vcR24gVg +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:jlX7n/s5f08LDVSFfpbM/g __PACKAGE__->load_components('InflateColumn::Serializer'); __PACKAGE__->add_columns( diff --git a/lib/Libki/Schema/DB/Result/Role.pm b/lib/Libki/Schema/DB/Result/Role.pm index 77e63b42..6febaa74 100644 --- a/lib/Libki/Schema/DB/Result/Role.pm +++ b/lib/Libki/Schema/DB/Result/Role.pm @@ -57,7 +57,7 @@ __PACKAGE__->table("roles"); =head2 role - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 1 =cut @@ -66,7 +66,7 @@ __PACKAGE__->add_columns( "id", { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, "role", - { data_type => "longtext", is_nullable => 1 }, + { data_type => "mediumtext", is_nullable => 1 }, ); =head1 PRIMARY KEY @@ -109,8 +109,8 @@ Composing rels: L -> user __PACKAGE__->many_to_many("users", "user_roles", "user"); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-04-27 09:23:45 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/EScGDz6Ax+fO+asu+G6sw +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:I0G2JZpQIrURRKmeedBoJA # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/lib/Libki/Schema/DB/Result/Setting.pm b/lib/Libki/Schema/DB/Result/Setting.pm index 2595918c..05811d8c 100644 --- a/lib/Libki/Schema/DB/Result/Setting.pm +++ b/lib/Libki/Schema/DB/Result/Setting.pm @@ -64,7 +64,7 @@ __PACKAGE__->table("settings"); =head2 value - data_type: 'longtext' + data_type: 'mediumtext' is_nullable: 0 =cut @@ -75,7 +75,7 @@ __PACKAGE__->add_columns( "name", { data_type => "varchar", is_nullable => 0, size => 191 }, "value", - { data_type => "longtext", is_nullable => 0 }, + { data_type => "mediumtext", is_nullable => 0 }, ); =head1 PRIMARY KEY @@ -93,8 +93,8 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("instance", "name"); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-04-27 09:23:45 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EqZtUoS/kZvDJlZBoafaPQ +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:L3jv3QXJPdbcPUJ5AR3QnA # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/lib/Libki/Schema/DB/Result/Transaction.pm b/lib/Libki/Schema/DB/Result/Transaction.pm index f543fa85..cae346de 100644 --- a/lib/Libki/Schema/DB/Result/Transaction.pm +++ b/lib/Libki/Schema/DB/Result/Transaction.pm @@ -83,7 +83,6 @@ __PACKAGE__->table("transactions"); =head2 amount_cents data_type: 'integer' - extra: {unsigned => 1} is_nullable: 0 =head2 currency @@ -132,7 +131,7 @@ __PACKAGE__->add_columns( "provider_payment_id", { data_type => "varchar", is_nullable => 1, size => 128 }, "amount_cents", - { data_type => "integer", extra => { unsigned => 1 }, is_nullable => 0 }, + { data_type => "integer", is_nullable => 0 }, "currency", { data_type => "char", default_value => "USD", is_nullable => 0, size => 3 }, "status", @@ -206,8 +205,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07053 @ 2025-12-19 15:32:17 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:C5vxDal47UNOSdaRn9nouQ +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wTt5kG0X0tOz/Mq55tR78w # You can replace this text with custom code or comments, and it will be preserved on regeneration diff --git a/lib/Libki/Schema/DB/Result/User.pm b/lib/Libki/Schema/DB/Result/User.pm index 3aa669ae..d6972231 100644 --- a/lib/Libki/Schema/DB/Result/User.pm +++ b/lib/Libki/Schema/DB/Result/User.pm @@ -152,6 +152,14 @@ __PACKAGE__->table("users"); is_nullable: 1 size: 191 +=head2 gratis_print_balance + + data_type: 'integer' + default_value: 0 + is_nullable: 0 + +Number of free prints remaining; represents pages or cents depending on system settings + =cut __PACKAGE__->add_columns( @@ -211,6 +219,8 @@ __PACKAGE__->add_columns( { data_type => "varchar", default_value => "", is_nullable => 1, size => 191 }, "creation_source", { data_type => "varchar", is_nullable => 1, size => 191 }, + "gratis_print_balance", + { data_type => "integer", default_value => 0, is_nullable => 0 }, ); =head1 PRIMARY KEY @@ -374,8 +384,8 @@ Composing rels: L -> role __PACKAGE__->many_to_many("roles", "user_roles", "role"); -# Created by DBIx::Class::Schema::Loader v0.07053 @ 2025-12-19 15:32:17 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ieWa+l+vWEQ/vWPaNZ37DQ +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-02-11 10:59:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tV3lA7kB36cKoCgfsJq4wg __PACKAGE__->add_columns( 'password' => { diff --git a/lib/Libki/Utils/Printing.pm b/lib/Libki/Utils/Printing.pm index 32564e7f..6e48a750 100644 --- a/lib/Libki/Utils/Printing.pm +++ b/lib/Libki/Utils/Printing.pm @@ -4,6 +4,7 @@ use Modern::Perl; use File::Slurp qw( write_file ); use File::Temp qw( tempfile ); +use JSON qw( to_json ); use PDF::API2; use Try::Tiny; @@ -148,13 +149,18 @@ sub create_print_job_and_file { =head2 calculate_job_cost -Helper function to calculate the cost of a print job for a given printer +Helper function to calculate the cost of a print job for a given printer. +Returns a list containing the calculated cost and the gratis discount applied. + + my ( $cost, $gratis_discount ) = calculate_job_cost( $c, { print_job => $job } ); =cut sub calculate_job_cost { my ( $c, $params ) = @_; + my $GratisPrintingMethod = $c->setting("GratisPrintingMethod"); + my $print_job = $params->{print_job}; my $print_file = $params->{print_file}; my $printer = $params->{printer}; @@ -175,9 +181,33 @@ sub calculate_job_cost { my $copies = $print_job->copies || 1; my $cpp = $printer->{cost_per_page} || 0; - my $cost = $copies * $pages * $cpp; + my $total_pages = $pages * $copies; + + my $gratis_discount = 0; + + if ( $GratisPrintingMethod eq 'pages' ) { + if ( $total_pages >= $c->user->gratis_print_balance ) { + $gratis_discount = $c->user->gratis_print_balance; + $total_pages -= $c->user->gratis_print_balance; + } else { + $gratis_discount = $total_pages; + $total_pages = 0; + } + } + + my $cost = $total_pages * $cpp; + + if ( $GratisPrintingMethod eq 'balance' ) { + if ( $cost >= $c->user->gratis_print_balance ) { + $gratis_discount = $c->user->gratis_print_balance; + $cost -= $c->user->gratis_print_balance; + } else { + $gratis_discount = $cost; + $cost = 0; + } + } - return $cost; + return ($cost, $gratis_discount); } =head2 cancel @@ -298,7 +328,7 @@ sub release { printer => $printer, }; - my $total_cost = calculate_job_cost( $c, + my ($total_cost, $gratis_discount) = calculate_job_cost( $c, { print_job => $print_job, printer => $printer, @@ -310,12 +340,33 @@ sub release { if ( $total_cost <= $user->funds ) { $user->debit_funds( $c, $total_cost ); + $user->gratis_print_balance( $user->gratis_print_balance - $gratis_discount ); + $print_job->status(PRINT_STATUS_PENDING); $c->model('DB')->txn_do( sub { $user->update(); $print_job->update(); + + # Add statistic indicating gratis discount was used + if ( $gratis_discount > 0 ) { + $c->model('DB::Statistic')->create( + { + instance => $c->instance, + username => $c->user->username, + action => 'GRATIS_DISCOUNT', + created_on => $c->now, + session_id => $c->sessionid, + info => to_json( + { + print_job_id => $print_job->id, + gratis_discount => $gratis_discount, + } + ), + } + ); + } } ); } diff --git a/lib/Libki/Utils/User.pm b/lib/Libki/Utils/User.pm index 426d6150..66737759 100644 --- a/lib/Libki/Utils/User.pm +++ b/lib/Libki/Utils/User.pm @@ -70,6 +70,7 @@ sub create_or_update_user { is_troublemaker => 'No', created_on => $now, updated_on => $now, + gratis_print_balance => $c->setting('GratisPrintingValue') || 0, } ); @@ -158,6 +159,7 @@ sub create_guest { updated_on => $now, category => $category, creation_source => 'local', + gratis_print_balance => $c->setting('GratisPrintingValueGuest') || 0, } ); diff --git a/root/dynamic/includes/index/printing.tt b/root/dynamic/includes/index/printing.tt index 84f1c264..42d1bb81 100644 --- a/root/dynamic/includes/index/printing.tt +++ b/root/dynamic/includes/index/printing.tt @@ -68,7 +68,32 @@
-

[% c.loc("Funds available for printing") %]: 0

+

[% c.loc("Funds available for printing") %]: 0

+ + [% IF c.setting("GratisPrintingValue") > 0 %] +

+ 0 + + [% IF c.setting("GratisPrintingMethod") == "balance" %] + [% c.loc("gratis funds remaining for the") %] + [% ELSE %] + [% c.loc("gratis pages remaining for the") %] + [% END %] + + [% IF c.setting("GratisPrintingResetFrequency") == "daily" %] + [% c.loc("day") %] + [% ELSIF c.setting("GratisPrintingResetFrequency") == "weekly" %] + [% c.loc("week") %] + [% ELSIF c.setting("GratisPrintingResetFrequency") == "monthly" %] + [% c.loc("month") %] + [% END %] +

+ + [% c.loc('All print job valus include any free printing you may have.') %] + [% c.loc('Print job costs may change if a released job uses some or all of your free printing balance') %] + + [% END %] + [% c.loc('If enough funds are available, printing will start immediately when the "Print" button is clicked.') %] @@ -251,7 +276,10 @@ $(document).ready(function() { const res = await fetch("/public/api/user/funds"); const data = await res.json(); user_funds = data.funds; + gratis_print_balance = data.gratis_print_balance; + $('#user-funds').html(formatter.format(user_funds)); + $('#gratis-print-balance').html(formatter.format(gratis_print_balance)); } catch (err) { console.error("Error fetching user funds:", err); } diff --git a/root/dynamic/templates/administration/index.tt b/root/dynamic/templates/administration/index.tt index bb628e72..bbd21272 100644 --- a/root/dynamic/templates/administration/index.tt +++ b/root/dynamic/templates/administration/index.tt @@ -54,6 +54,7 @@ [% c.loc("Daily minutes") %] [% c.loc("Session minutes") %] [% c.loc("Funds") %] + [% c.loc("Gratis print balance") %] [% c.loc("Status") %] [% c.loc("Notes") %] [% c.loc("Troublemaker") %] @@ -570,6 +571,13 @@ [% c.loc("Use period as separator when editing funds.") %]
+ [% IF GratisPrintingValue > 0 %] +
+ + +
+ [% END %] +
+
+ [% c.loc("pages or funds") %] +
+
+ [% c.loc("Default number of gratis pages or funds to give users for free printing.") %] + + +
+ +
+ [% c.loc("pages or funds") %] +
+
+ [% c.loc("Default number of gratis pages or funds to give guests for free printing.") %] + + +
+ +
+ [% c.loc("Method to use for gratis printing.") %] + + +
+ +
+ [% c.loc("Frequency to reset gratis printing to the default value.") %] + [% c.loc("Daily will reset each night, monthly will reset on the first day of the month, and weekly will reset on the day of the week selected in Gratis printing reset day.") %] + +
+ +
+ +
+ [% c.loc("Day of the week to reset gratis printing.") %] +
+ + +
diff --git a/script/cronjobs/libki_nightly.pl b/script/cronjobs/libki_nightly.pl index 3513425a..5ab2e7d4 100755 --- a/script/cronjobs/libki_nightly.pl +++ b/script/cronjobs/libki_nightly.pl @@ -150,6 +150,35 @@ } } +## Reset gratis printing +my $reset_frequency = $c->setting('GratisPrintingResetFrequency'); +my $reset_day = $c->setting('GratisPrintingResetDay'); +if ( $reset_frequency && $reset_day ) { + my $today = DateTime->today( time_zone => $ENV{LIBKI_TZ} ); + + my $do_reset = 0; + if ( $reset_frequency eq 'daily' ) { + $do_reset = 1; + } + elsif ( $reset_frequency eq 'weekly' ) { + if ( $today->day_name eq $reset_day ) { + $do_reset = 1; + } + } + elsif ( $reset_frequency eq 'monthly' ) { + if ( $today->day == 1 ) { + $do_reset = 1; + } + } + + if ( $do_reset ) { + my $gratis_printing_value = $c->setting('GratisPrintingValue') || 0; + my $gratis_printing_value_guest = $c->setting('GratisPrintingValueGuest') || 0; + + $c->model('DB::User')->search( { is_guest => 'No' } )->update( { gratis_print_balance => $gratis_printing_value } ); + } +} + ## Clear out old print jobs and print files my @print_retention_days = $c->model('DB::Setting')->search( { name => 'PrintJobRetentionDays' } ); foreach my $prd (@print_retention_days) {