Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,97 @@ jobs:
run: |
cd galette-core/galette/plugins/plugin-objectslend
../../vendor/bin/phpunit --test-suffix=.php --bootstrap tests/TestsBootstrap.php --no-coverage --process-isolation tests/GaletteObjectsLend/

upgrade:
runs-on: ubuntu-latest

strategy:
matrix:
db-image: ['mysql:8.4', 'mariadb:11', 'postgres:17']
fail-fast: false

env:
DB: ${{ matrix.db-image }}

services:
# Label used to access the service container
db:
# Docker Hub image
image: ${{ matrix.db-image }}
# Provide env variables for both mysql and pgsql
env:
POSTGRES_USER: galette_tests
POSTGRES_PASSWORD: g@l3tte
POSTGRES_DB: galette_tests
MYSQL_USER: galette_tests
MYSQL_PASSWORD: g@l3tte
MYSQL_ROOT_PASSWORD: g@l3tte
MYSQL_DATABASE: galette_tests
# Open network ports for both mysql and pgsql
ports:
- 3306:3306
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd="bash -c 'if [[ -n $(command -v pg_isready) ]]; then pg_isready; else if [[ -n $(command -v mysqladmin) ]]; then mysqladmin ping; else mariadb-admin ping; fi fi'"
--health-interval=10s
--health-timeout=5s
--health-retries=10

name: Upgrade from previous release on ${{ matrix.db-image }}

steps:
- name: PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: composer, pecl
coverage: none
extensions: apcu
ini-values: apc.enable_cli=1

- name: Build Galette
uses: galette/.github/actions/build-galette@main
with:
php-version: '8.4'

- name: Checkout plugin
uses: actions/checkout@v7
with:
path: galette-core/galette/plugins/plugin-objectslend
fetch-depth: 0

- name: Find previous release
run: |
cd galette-core/galette/plugins/plugin-objectslend
echo "PREVIOUS_RELEASE=$(git tag --list '[0-9]*' --sort=-v:refname --no-contains HEAD | head -n1)" >> $GITHUB_ENV

- name: Install previous release for PostgreSQL
env:
PGPASSWORD: g@l3tte
run: |
cd galette-core
bin/console galette:install -v --dbtype=pgsql --dbhost=localhost --dbname=galette_tests --dbuser=galette_tests --dbpass=g@l3tte --admin=admin --password=admin --no-interaction -w
git -C galette/plugins/plugin-objectslend show "$PREVIOUS_RELEASE:scripts/pgsql.sql" \
| psql -v ON_ERROR_STOP=1 -h localhost -U galette_tests galette_tests
if: startsWith(matrix.db-image, 'postgres')

- name: Install previous release for MariaDB
run: |
cd galette-core
mysql -e 'create database IF NOT EXISTS galette_tests;' -u galette_tests --password=g@l3tte -h 127.0.0.1 -P 3306
bin/console galette:install -v --dbtype=mysql --dbhost=127.0.0.1 --dbname=galette_tests --dbuser=galette_tests --dbpass=g@l3tte --admin=admin --password=admin --no-interaction -w
git -C galette/plugins/plugin-objectslend show "$PREVIOUS_RELEASE:scripts/mysql.sql" \
| mysql -u galette_tests --password=g@l3tte -h 127.0.0.1 -P 3306 galette_tests
if: startsWith(matrix.db-image, 'mysql') || startsWith(matrix.db-image, 'mariadb')

- name: Upgrade
run: |
cd galette-core
bin/console galette:plugins:install-db --no-interaction plugin-objectslend | tee upgrade.log
grep -q 'Database for plugin "plugin-objectslend" upgraded' upgrade.log

- name: Unit tests
run: |
cd galette-core/galette/plugins/plugin-objectslend
../../vendor/bin/phpunit --test-suffix=.php --bootstrap tests/TestsBootstrap.php --no-coverage --process-isolation tests/GaletteObjectsLend/
68 changes: 68 additions & 0 deletions lib/GaletteObjectsLend/LendService.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,74 @@ public function changeStatus(
);
}

/**
* Give back objects held by a member who is being removed
*
* Each object goes back to the last in stock status it had, or to the
* first active one. Without any in stock status, the object stays as is,
* and loses its borrower with the member. Rights to remove the member
* have already been checked.
*
* @param int $member_id Member ID
*/
public function giveBackMemberObjects(int $member_id): void
{
$select = $this->zdb->select(LEND_PREFIX . LendObject::TABLE, 'o')
->columns([LendObject::PK])
->join(
['r' => PREFIX_DB . LEND_PREFIX . LendRent::TABLE],
'o.' . LendRent::PK . ' = r.' . LendRent::PK,
[]
)
->where(['r.adherent_id' => $member_id, 'r.date_end' => null]);

$object_ids = [];
foreach ($this->zdb->execute($select) as $row) {
$object_ids[] = (int)$row[LendObject::PK];
}
if ($object_ids === []) {
return;
}

$stock_statuses = $this->getStatuses()->getActiveStockStatuses();
$this->inTransaction(function () use ($object_ids, $stock_statuses): void {
foreach ($object_ids as $object_id) {
$status_id = $this->getLastStockStatus($object_id) ?? ($stock_statuses[0] ?? null)?->getId();
if ($status_id === null) {
continue;
}
$this->openRent(
$this->getObject($object_id),
$status_id,
null,
_T("Returned on member removal", "objectslend")
);
}
});
}

/**
* Last active in stock status an object had
*
* @param int $object_id Object ID
*/
private function getLastStockStatus(int $object_id): ?int
{
$select = $this->zdb->select(LEND_PREFIX . LendRent::TABLE, 'r')
->columns([LendStatus::PK])
->join(
['s' => PREFIX_DB . LEND_PREFIX . LendStatus::TABLE],
'r.' . LendStatus::PK . ' = s.' . LendStatus::PK,
[]
)
->where(['r.' . LendObject::PK => $object_id, 's.in_stock' => 1, 's.is_active' => 1])
->order(['r.date_begin DESC', 'r.' . LendRent::PK . ' DESC'])
->limit(1);

$row = $this->zdb->execute($select)->current();
return $row ? (int)$row[LendStatus::PK] : null;
}

/**
* Close current rents, open a new one and set it as the object current one
*
Expand Down
54 changes: 54 additions & 0 deletions lib/GaletteObjectsLend/PluginEventProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/**
* This file is part of Galette Objects Lend plugin (https://galette.eu).
* SPDX-FileCopyrightText: Copyright © 2013-2026 The Galette Team
* SPDX-License-Identifier: GPL-3.0-or-later
*/

declare(strict_types=1);

namespace GaletteObjectsLend;

use Galette\Entity\Adherent;
use Galette\Events\GaletteEvent;
use League\Event\ListenerRegistry;
use League\Event\ListenerSubscriber;
use Psr\Container\ContainerInterface;

/**
* Objects lend listeners on core events
*
* @author Johan Cwiklinski <johan@x-tnd.be>
*/
class PluginEventProvider implements ListenerSubscriber
{
/**
* Constructor
*
* Built while plugins are loaded: the lend service is resolved only
* when an event is emitted.
*
* @param ContainerInterface $container Container
*/
public function __construct(private readonly ContainerInterface $container)
{
}

/**
* Set up listeners
*
* @param ListenerRegistry $acceptor Listener
*/
public function subscribeListeners(ListenerRegistry $acceptor): void
{
$acceptor->subscribeTo(
'member.before_remove',
function (GaletteEvent $event): void {
/** @var \ArrayObject<string, mixed> $member */
$member = $event->getObject();
$this->container->get(LendService::class)->giveBackMemberObjects((int)$member[Adherent::PK]);
}
);
}
}
10 changes: 10 additions & 0 deletions lib/GaletteObjectsLend/PluginGaletteObjectslend.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,14 @@ public function isInstalled(): bool
&& $this->zdb->tableExists(LEND_PREFIX . ObjectPicture::TABLE)
;
}

/**
* Database version of tables installed before versions tracking
*
* Parameters table has been dropped in 1.1, when preferences moved to core.
*/
public function getLegacyDbVersion(): ?float
{
return $this->zdb->tableExists(LEND_PREFIX . 'parameters') ? 1.0 : null;
}
}
53 changes: 31 additions & 22 deletions scripts/mysql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS galette_lend_category;
CREATE TABLE galette_lend_category (
category_id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(100) COLLATE utf8_general_ci NOT NULL,
name varchar(100) NOT NULL,
is_active tinyint(1) NOT NULL,
PRIMARY KEY (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

DROP TABLE IF EXISTS galette_lend_status;
CREATE TABLE galette_lend_status (
Expand All @@ -22,7 +22,7 @@ CREATE TABLE galette_lend_status (
is_active tinyint(1) NOT NULL,
rent_day_number INT NULL DEFAULT NULL,
PRIMARY KEY (status_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

DROP TABLE IF EXISTS galette_lend_rents;
CREATE TABLE galette_lend_rents (
Expand All @@ -36,10 +36,13 @@ CREATE TABLE galette_lend_rents (
comments varchar(200) NOT NULL,
PRIMARY KEY (rent_id),
KEY date_begin (date_begin),
FOREIGN KEY FK_rent_adherent_1 (adherent_id) REFERENCES galette_adherents (id_adh) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY FK_rent_status_1 (status_id) REFERENCES galette_lend_status (status_id) ON DELETE NO ACTION ON UPDATE NO ACTION,
FOREIGN KEY FK_rent_object_1 (object_id) REFERENCES galette_lend_objects (object_id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CONSTRAINT galette_lend_rents_adherent_id_fkey FOREIGN KEY (adherent_id)
REFERENCES galette_adherents (id_adh) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT galette_lend_rents_status_id_fkey FOREIGN KEY (status_id)
REFERENCES galette_lend_status (status_id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT galette_lend_rents_object_id_fkey FOREIGN KEY (object_id)
REFERENCES galette_lend_objects (object_id) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

DROP TABLE IF EXISTS galette_lend_objects;
CREATE TABLE galette_lend_objects (
Expand All @@ -57,17 +60,31 @@ CREATE TABLE galette_lend_objects (
nb_available INT NULL,
rent_id int(10) unsigned NULL DEFAULT NULL,
PRIMARY KEY (object_id),
FOREIGN KEY FK_rent_category_1 (category_id) REFERENCES galette_lend_category (category_id) ON DELETE NO ACTION ON UPDATE NO ACTION,
FOREIGN KEY FK_object_rent_1 (rent_id) REFERENCES galette_lend_rents (rent_id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CONSTRAINT galette_lend_objects_category_id_fkey FOREIGN KEY (category_id)
REFERENCES galette_lend_category (category_id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT galette_lend_objects_rent_id_fkey FOREIGN KEY (rent_id)
REFERENCES galette_lend_rents (rent_id) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

DROP TABLE IF EXISTS galette_lend_pictures;
CREATE TABLE galette_lend_pictures (
object_id int(11) NOT NULL,
object_id int(10) unsigned NOT NULL,
picture mediumblob NOT NULL,
format varchar(10) NOT NULL DEFAULT '',
PRIMARY KEY (object_id),
CONSTRAINT galette_lend_pictures_object_id_fkey FOREIGN KEY (object_id)
REFERENCES galette_lend_objects (object_id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

DROP TABLE IF EXISTS galette_lend_categories_pictures;
CREATE TABLE galette_lend_categories_pictures (
category_id int(10) unsigned NOT NULL,
picture mediumblob NOT NULL,
format varchar(10) CHARACTER SET utf8 NOT NULL DEFAULT '',
PRIMARY KEY (object_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
format varchar(10) NOT NULL DEFAULT '',
PRIMARY KEY (category_id),
CONSTRAINT galette_lend_categories_pictures_category_id_fkey FOREIGN KEY (category_id)
REFERENCES galette_lend_category (category_id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Garage A (exemple)', 1, 1);
INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Maison B (exemple)', 1, 1);
Expand All @@ -78,12 +95,4 @@ INSERT INTO galette_lend_status (status_text, in_stock, is_active, rent_day_numb
INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Vendu (exemple)', 0, 1);
INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Detruit (exemple)', 0, 1);

DROP TABLE IF EXISTS galette_lend_categories_pictures;
CREATE TABLE IF NOT EXISTS galette_lend_categories_pictures (
category_id int(11) NOT NULL,
picture mediumblob NOT NULL,
format varchar(10) CHARACTER SET utf8 NOT NULL DEFAULT '',
PRIMARY KEY (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS=1;
Loading
Loading