Compra de productes
Compra de productes
A l'aplicació s'implementa la funcionalitat de comprar els productes dins del cistell. L'objectiu d'aquesta funcionalitat és desar la compra i els seus detalls a la BD per després mostrar-los en un històric de compres.
Inicialització de la sessió i connexió a la BD
Sempre que es treballa amb sessions, el primer que hem de fer és iniciar-la abans d'escriure el codi HTML
session_start();
En aquest cas, si el cistell no està definit (o està buit) no el crearem, sinó que redirigirem a l'usuari al catàleg
if (!isset($_SESSION['cistell']) || empty($_SESSION['cistell'])) { // En cas que s'accedeixi a la pàgina amb el cistell buit
header("Location: cataleg.php"); exit;}
A més de requerir la connexió d'escriptura a la BD i el fitxer funcions.php on es troba la funcio calcularPreus
require_once "./connexioBD/connexioRW.php"; // Requerim un fitxer on definim la funció per calcular els preus i descomptes require_once "funcions.php";
Inici de la transacció
En aquest cas realitzarem tot el procés mitjançant una transacció. Una transacció ens permet rectificar tot el que s'ha fet abans de que es produis un error.
En aquest codi estarem insertant i actualitzant molts registres a la BD, i no volem que un registre es mantingui quan realment ha sigut una compra incorrecte i no s'ha pogut processar correctament
Gràcies a una transacció, en cas de que certes operacions es realitzin correctament, en cas d'haver-hi algun error en alguna futura, totes les que s'han realitzat correctament seran desfetes.
$pdo->beginTransaction();
A més, iniciarem una variable per calcular el total de compra i una array on desarem els detalls dels productes
$total_compra = 0; // Definim el total de la compra $detalls_compra = []; // Definim una array on especificarem els detalls de cada compra (quantitat, preu, etc)
A partir d'aquest moment, per cada producte dins del cistell realitzarem una sèrie de verificacions i càlculs
Verificació de la quantitat de productes
Per cada producte dins del cistell haurem de verificar que la quantitat sigui correcte
foreach ($_SESSION['cistell'] as $id_producte => $producte) { // Per cada producte
$quantitat = (int)$producte['quantitat']; // Agafem la quantitat
if ($quantitat <= 0){ // En cas que la quantitat sigui 0 o menys
throw new Exception("Quantitat no vàlida"); // Llençem una excepció per invocar el rollback
}
Recuperació de les dades del producte
A partir de la ID del producte del cistell, recuperarem les seves dades i verificarem que el producte realment sigui correcte
// Recuperem el preu i l'stock del producte del cistell
$stmt = $pdo->prepare("SELECT preu, estoc FROM productes WHERE id = :id_producte");
$stmt->bindParam(":id_producte",$id_producte);
$stmt->execute();
$db_producte = $stmt->fetch();
// Si la consulta no retorna res, voldrà dir que l'ID no existeix
if (!$db_producte){
throw new Exception("El producte no existeix");
}
Verificació de quantitat de productes amb stock
Si, tot i havent-hi verificacions d’stock real i visible, es dóna el cas que al cistell hi ha més productes que stock real, llençarem una excepció
// Si per qualsevol motiu, la quantitat del cistell és superior a l'stock, llençarem una excepció
if ($quantitat > $db_producte['estoc']){
throw new Exception("No hi ha prou estoc del producte ID $id_producte");
}
Càlcul de subtotals i el total
De la mateixa manera que es fa amb el cistell, calculem el subtotal de cada producte amb la funció calcularPreus i anem sumant el subtotal al total de compra per cada producte.
// Calculem el preu dels productes aplicant un possible descompte $total_producte = calcularPreus($db_producte['preu'], $quantitat); // És una array amb: 1. Preu original sense descompte 2. Preu amb descompte 3. Descompte aplicat $total_compra = $total_compra + $total_producte['total_final']; // Per cada producte, sumem el seu import al total de la compra
Desar els detalls del producte dins d'una array
Per cada producte comprat, desarem els seus detalls dins d'una array. D'aquesta manera, posteriorment podrem inserir aquestes dades dins la taula compres_detall.
Si es compren 2 productes, hi haurà 2 vectors dins d'aquesta array.
// Desem dels detalls de la compra
$detalls_compra[] = [ //[] a una array indica afegir al final un objecte (en aquest cas una altra array). En aquesta array hi haura tants objectes com productes comprats (no quantitat, sinó "quin producte")
'id_producte' => $id_producte,
'quantitat' => $quantitat,
'preu_unitari' => $db_producte['preu'],
'total_final' => $total_producte['total_final']
];
Tot aquest procés es realitza per cada producte dins del cistell
Inserció de dades a la BD
Un cop recuperats tots els productes, inserirem a la taula compres el total de la compra i la data de compra. És important recuperar la ID d’aquesta compra per després inserir-la als registres de compres_detall (ja que recordem que és una clau forània)
// Registrar la compra
$stmt = $pdo->prepare("INSERT INTO compres (data_compra, total) VALUES (NOW(), :totalCompra)");
$stmt->bindParam(":totalCompra",$total_compra);
$stmt->execute();
$compra_id = $pdo->lastInsertId(); // Desem la ID per relacionar els detalls de la compra a aquesta compra
A continuació, a partir de l’array de detalls de compres, per cada producte comprat, inserirem els seus detalls (quantitat comprada, preus, etc) a la taula compres_detall, i la ID de compra la recuperem de l’anterior registre a la taula compres. Si a l'array hi ha 2 vectors (2 objectes comprats) s'inseriràn 2 registres a la taula
// Registra els detalls de la compra i actualitzar l'stock dels productes
foreach ($detalls_compra as $detall) { // (Per cada producte comprat) Per cada detall de la compra, el qual és un tipus de producte $stmt = $pdo->prepare( // Insertarem els detalls a una base de dades "INSERT INTO compres_detall (compra_id, producte_id, quantitat, preu_unitari, total_producte) VALUES (:compra_id, :producte_id, :quantitat, :preu_unitari, :total_producte)" ); $stmt->bindParam(":compra_id", $compra_id); // Relacionem els detalls de la compra a la compra $stmt->bindParam(":producte_id", $detall['id_producte']); // Definim quin producte és $stmt->bindParam(":quantitat", $detall['quantitat']); // La quantitat $stmt->bindParam(":preu_unitari", $detall['preu_unitari']); // El preu unitari $stmt->bindParam(":total_producte", $detall['total_final']); // El total d'aquell conjunt d'unitats (incloent-hi descompte)$stmt->execute();
Actualització de l'stock dels productes
Un cop inserides les dades de compra i els seus detalls, únicament caldrà actualitzar l'stock dels productes comprats. Les dades les recuperem de l'array de detalls, ja que incorpora la ID del producte i la quantitat comprada.
// Actualitzem l'stock del producte
$stmt = $pdo->prepare("UPDATE productes SET estoc = estoc - :quantitat WHERE id = :id_producte");
$stmt->bindParam(":quantitat",$detall['quantitat']);
$stmt->bindParam(":id_producte",$detall['id_producte']);
$stmt->execute();
Confirmació de la transacció i gestió d'errors
Si tot el procés comentat anteriorment s'ha realitzat sense cap error, es farà un commit de la transacció i tancarem la sessió
$pdo->commit(); // Confirmem la transacció
// Un cop comprats els productes, tanquem la sessió
$_SESSION['cistell'] = array();
session_destroy();
En cas que succeeixi qualsevol error en qualsevol moment de la transacció, realitzarem un rollback per desfer tots els canvis realitzats.
} catch (Exception $e) {
if ($pdo->inTransaction()){ // En cas d'haver-hi qualsevol excepció en la transacció $pdo->rollBack(); // Fem un rollback per no inserir dades a mitges o errònies } die("Error en la compra: " . htmlspecialchars($e->getMessage()));}
Verificació de compra
Quan es realitza la compra correctament, es mostra un HTML indicant-ho i informant del cost total de la compra. A més, s'afageix un botó de navegació al catàleg.

Codi complet
<?php
// Iniciem la sessió i requerim la connexió a la BD
session_start();
require_once "./connexioBD/connexioRW.php";
// Requerim un fitxer on definim la funció per calcular el descompte
require_once "funcions.php";
if (!isset($_SESSION['cistell']) || empty($_SESSION['cistell'])) { // En cas que s'accedeixi a la pàgina amb el cistell buit
header("Location: cataleg.php");
exit;
}
try {
$pdo->beginTransaction(); // Començem una transacció. Al realitzar diverses accions, en cas d'haver-hi algun error és imperatiu tenir la possibilitat de fer un rollback
$total_compra = 0; // Definim el total de la compra
$detalls_compra = []; // Definim una array on especificarem els detalls de cada compra (quantitat, preu, etc)
// Validar stock i calcular totals
foreach ($_SESSION['cistell'] as $id_producte => $producte) { // Per cada producte
$quantitat = (int)$producte['quantitat']; // Agafem la quantitat
if ($quantitat <= 0){ // En cas que la quantitat sigui 0 o menys
throw new Exception("Quantitat no vàlida"); // Llençem una excepció per invocar el rollback
}
// Recuperem el preu i l'stock del producte del cistell
$stmt = $pdo->prepare("SELECT preu, estoc FROM productes WHERE id = :id_producte");
$stmt->bindParam(":id_producte",$id_producte);
$stmt->execute();
$db_producte = $stmt->fetch();
// Si la consulta no retorna res, voldrà dir que l'ID no existeix
if (!$db_producte){
throw new Exception("El producte no existeix");
}
// Si per qualsevol motiu, la quantitat del cistell és superior a l'stock, llençarem una excepció
if ($quantitat > $db_producte['estoc']){
throw new Exception("No hi ha prou estoc del producte ID $id_producte");
}
// Calculem el preu dels productes aplicant un possible descompte
$total_producte = calcularPreus($db_producte['preu'], $quantitat); // És una array amb: 1. Preu original sense descompte 2. Preu amb descompte 3. Descompte aplicat
$total_compra = $total_compra + $total_producte['total_final']; // Per cada producte, sumem el seu import al total de la compra
// Desem dels detalls de la compra
$detalls_compra[] = [ //[] a una array indica afegir al final un objecte (en aquest cas una altra array). En aquesta array hi haura tants objectes com productes comprats (no quantitat, sinó "quin producte")
'id_producte' => $id_producte,
'quantitat' => $quantitat,
'preu_unitari' => $db_producte['preu'],
'total_final' => $total_producte['total_final']
];
} // Tot aquest procés es realitza per cada producte dins del cistell
// Registrar la compra
$stmt = $pdo->prepare("INSERT INTO compres (data_compra, total) VALUES (NOW(), :totalCompra)");
$stmt->bindParam(":totalCompra",$total_compra);
$stmt->execute();
$compra_id = $pdo->lastInsertId(); // Desem la ID per relacionar els detalls de la compra a aquesta compra
// Registra els detalls de la compra i actualitzar l'stock dels productes
foreach ($detalls_compra as $detall) { // (Per cada producte comprat) Per cada detall de la compra, el qual és un tipus de producte
$stmt = $pdo->prepare( // Insertarem els detalls a una base de dades
"INSERT INTO compres_detall
(compra_id, producte_id, quantitat, preu_unitari, total_producte)
VALUES (:compra_id, :producte_id, :quantitat, :preu_unitari, :total_producte)"
);
$stmt->bindParam(":compra_id", $compra_id); // Relacionem els detalls de la compra a la compra
$stmt->bindParam(":producte_id", $detall['id_producte']); // Definim quin producte és
$stmt->bindParam(":quantitat", $detall['quantitat']); // La quantitat
$stmt->bindParam(":preu_unitari", $detall['preu_unitari']); // El preu unitari
$stmt->bindParam(":total_producte", $detall['total_final']); // El total d'aquell conjunt d'unitats (incloent-hi descompte)
$stmt->execute();
// Actualitzem l'stock del producte
$stmt = $pdo->prepare("UPDATE productes SET estoc = estoc - :quantitat WHERE id = :id_producte");
$stmt->bindParam(":quantitat",$detall['quantitat']);
$stmt->bindParam(":id_producte",$detall['id_producte']);
$stmt->execute();
} // Si hem comprat ratolins i teclats, inserirem dues noves files a la taula, indicant quants ratolins i teclats, a quin preu cadascun i el total de tots els productes
$pdo->commit(); // Confirmem la transacció
// Un cop comprats els productes, tanquem la sessió
$_SESSION['cistell'] = array();
session_destroy();
} catch (Exception $e) {
if ($pdo->inTransaction()){ // En cas d'haver-hi qualsevol excepció en la transacció
$pdo->rollBack(); // Fem un rollback per no inserir dades a mitges o errònies
}
die("Error en la compra: " . htmlspecialchars($e->getMessage()));
}
?>
<!-- HTML per mostrar la confirmació -->
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<title>Compra realitzada</title>
<link rel="stylesheet" href="./css/stylesCompra.css"> <!-- Definim el full d'estils -->
</head>
<body>
<h1>Compra realitzada correctament</h1>
<p>Import total: <strong><?php echo number_format($total_compra,2); ?> €</strong></p>
<a href="cataleg.php">Tornar al catàleg</a>
</body>
</html>