prepare(" SELECT COUNT(*) FROM samples WHERE idcompany = :idcompany AND YEAR(created_at) = :year "); $stmt->execute([ ':idcompany' => $idcompany, ':year' => $year, ]); $nextNumber = ((int) $stmt->fetchColumn()) + 1; return 'SMP-' . $year . '-' . str_pad((string) $nextNumber, 5, '0', STR_PAD_LEFT); } $sampleStatuses = [ 'draft' => 'Draft', 'active' => 'Active', 'archived' => 'Archived', 'submitted' => 'Submitted', 'under_testing' => 'Under Testing', 'completed' => 'Completed', 'cancelled' => 'Cancelled', ]; $sampleSources = [ 'manual' => 'Manual', 'xls_import' => 'XLS Import', 'json_import' => 'JSON Import', 'api' => 'API', 'smarttrf' => 'SmartTRF', ]; /* * AJAX actions. */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { $action = $_POST['action']; try { if ($action === 'save_sample') { $idsample = isset($_POST['idsample']) ? (int) $_POST['idsample'] : 0; $idcompany = isset($_POST['idcompany']) ? (int) $_POST['idcompany'] : 0; $idbrand = !empty($_POST['idbrand']) ? (int) $_POST['idbrand'] : null; $iddepartment = !empty($_POST['iddepartment']) ? (int) $_POST['iddepartment'] : null; $idproducer = !empty($_POST['idproducer']) ? (int) $_POST['idproducer'] : null; $idsupplier = !empty($_POST['idsupplier']) ? (int) $_POST['idsupplier'] : null; $sampleCode = trim($_POST['sample_code'] ?? ''); $externalSampleId = trim($_POST['external_sample_id'] ?? ''); $articleNo = trim($_POST['article_no'] ?? ''); $poNo = trim($_POST['po_no'] ?? ''); $season = trim($_POST['season'] ?? ''); $styleNo = trim($_POST['style_no'] ?? ''); $styleName = trim($_POST['style_name'] ?? ''); $model = trim($_POST['model'] ?? ''); $sampleDescription = trim($_POST['sample_description'] ?? ''); $productCategory = trim($_POST['product_category'] ?? ''); $productType = trim($_POST['product_type'] ?? ''); $color = trim($_POST['color'] ?? ''); $size = trim($_POST['size'] ?? ''); $gender = trim($_POST['gender'] ?? ''); $ageGroup = trim($_POST['age_group'] ?? ''); $fiberContent = trim($_POST['fiber_content'] ?? ''); $materialDescription = trim($_POST['material_description'] ?? ''); $claimedWeight = trim($_POST['claimed_weight'] ?? ''); $productStandard = trim($_POST['product_standard'] ?? ''); $productionStage = trim($_POST['production_stage'] ?? ''); $countryOfOrigin = !empty($_POST['country_of_origin']) ? (int) $_POST['country_of_origin'] : null; $status = $_POST['status'] ?? 'draft'; if ($idcompany <= 0) { jsonResponse([ 'success' => false, 'message' => 'Company is required.' ]); } if ($sampleDescription === '') { jsonResponse([ 'success' => false, 'message' => 'Sample description is required.' ]); } if (!array_key_exists($status, $sampleStatuses)) { $status = 'draft'; } /* * Check company exists. */ $stmt = $db->prepare(" SELECT COUNT(*) FROM companies WHERE idcompany = :idcompany "); $stmt->execute([':idcompany' => $idcompany]); if ((int) $stmt->fetchColumn() === 0) { jsonResponse([ 'success' => false, 'message' => 'Selected company does not exist.' ]); } /* * Generate sample code if empty. */ if ($sampleCode === '') { $sampleCode = generateSampleCode($db, $idcompany); } /* * Check brand belongs to company. */ if ($idbrand !== null) { $stmt = $db->prepare(" SELECT COUNT(*) FROM brands WHERE idbrand = :idbrand AND idcompany = :idcompany "); $stmt->execute([ ':idbrand' => $idbrand, ':idcompany' => $idcompany, ]); if ((int) $stmt->fetchColumn() === 0) { jsonResponse([ 'success' => false, 'message' => 'Selected brand does not belong to the selected company.' ]); } } /* * Check department belongs to company and is compatible with selected brand. */ if ($iddepartment !== null) { if ($idbrand !== null) { $stmt = $db->prepare(" SELECT COUNT(*) FROM departments WHERE iddepartment = :iddepartment AND idcompany = :idcompany AND (idbrand = :idbrand OR idbrand IS NULL) "); $stmt->execute([ ':iddepartment' => $iddepartment, ':idcompany' => $idcompany, ':idbrand' => $idbrand, ]); } else { $stmt = $db->prepare(" SELECT COUNT(*) FROM departments WHERE iddepartment = :iddepartment AND idcompany = :idcompany "); $stmt->execute([ ':iddepartment' => $iddepartment, ':idcompany' => $idcompany, ]); } if ((int) $stmt->fetchColumn() === 0) { jsonResponse([ 'success' => false, 'message' => 'Selected department is not compatible with the selected company/brand.' ]); } } /* * Check producer/supplier belong to the same company. */ foreach ( [ 'producer' => $idproducer, 'supplier' => $idsupplier, ] as $partnerLabel => $partnerId ) { if ($partnerId !== null) { $stmt = $db->prepare(" SELECT COUNT(*) FROM business_partners WHERE idpartner = :idpartner AND idcompany = :idcompany "); $stmt->execute([ ':idpartner' => $partnerId, ':idcompany' => $idcompany, ]); if ((int) $stmt->fetchColumn() === 0) { jsonResponse([ 'success' => false, 'message' => 'Selected ' . $partnerLabel . ' does not belong to the selected company.' ]); } } } /* * Check duplicate sample code inside the same company. */ if ($idsample > 0) { $stmt = $db->prepare(" SELECT COUNT(*) FROM samples WHERE idcompany = :idcompany AND sample_code = :sample_code AND idsample <> :idsample "); $stmt->execute([ ':idcompany' => $idcompany, ':sample_code' => $sampleCode, ':idsample' => $idsample, ]); } else { $stmt = $db->prepare(" SELECT COUNT(*) FROM samples WHERE idcompany = :idcompany AND sample_code = :sample_code "); $stmt->execute([ ':idcompany' => $idcompany, ':sample_code' => $sampleCode, ]); } if ((int) $stmt->fetchColumn() > 0) { jsonResponse([ 'success' => false, 'message' => 'Sample code already exists for this company.' ]); } $oldStatus = null; if ($idsample > 0) { $stmt = $db->prepare(" SELECT status FROM samples WHERE idsample = :idsample LIMIT 1 "); $stmt->execute([':idsample' => $idsample]); $oldStatus = $stmt->fetchColumn(); $sql = " UPDATE samples SET idcompany = :idcompany, idbrand = :idbrand, iddepartment = :iddepartment, idproducer = :idproducer, idsupplier = :idsupplier, sample_code = :sample_code, external_sample_id = :external_sample_id, article_no = :article_no, po_no = :po_no, season = :season, style_no = :style_no, style_name = :style_name, model = :model, sample_description = :sample_description, product_category = :product_category, product_type = :product_type, color = :color, size = :size, gender = :gender, age_group = :age_group, fiber_content = :fiber_content, material_description = :material_description, claimed_weight = :claimed_weight, product_standard = :product_standard, production_stage = :production_stage, country_of_origin = :country_of_origin, status = :status, updated_at = NOW() WHERE idsample = :idsample "; $stmt = $db->prepare($sql); $stmt->execute([ ':idcompany' => $idcompany, ':idbrand' => $idbrand, ':iddepartment' => $iddepartment, ':idproducer' => $idproducer, ':idsupplier' => $idsupplier, ':sample_code' => $sampleCode, ':external_sample_id' => $externalSampleId !== '' ? $externalSampleId : null, ':article_no' => $articleNo !== '' ? $articleNo : null, ':po_no' => $poNo !== '' ? $poNo : null, ':season' => $season !== '' ? $season : null, ':style_no' => $styleNo !== '' ? $styleNo : null, ':style_name' => $styleName !== '' ? $styleName : null, ':model' => $model !== '' ? $model : null, ':sample_description' => $sampleDescription, ':product_category' => $productCategory !== '' ? $productCategory : null, ':product_type' => $productType !== '' ? $productType : null, ':color' => $color !== '' ? $color : null, ':size' => $size !== '' ? $size : null, ':gender' => $gender !== '' ? $gender : null, ':age_group' => $ageGroup !== '' ? $ageGroup : null, ':fiber_content' => $fiberContent !== '' ? $fiberContent : null, ':material_description' => $materialDescription !== '' ? $materialDescription : null, ':claimed_weight' => $claimedWeight !== '' ? $claimedWeight : null, ':product_standard' => $productStandard !== '' ? $productStandard : null, ':production_stage' => $productionStage !== '' ? $productionStage : null, ':country_of_origin' => $countryOfOrigin, ':status' => $status, ':idsample' => $idsample, ]); if ($oldStatus !== $status) { $stmt = $db->prepare(" INSERT INTO sample_status_history ( idsample, old_status, new_status, note, changed_by, created_at ) VALUES ( :idsample, :old_status, :new_status, :note, :changed_by, NOW() ) "); $stmt->execute([ ':idsample' => $idsample, ':old_status' => $oldStatus, ':new_status' => $status, ':note' => 'Status changed from sample form.', ':changed_by' => $iduserlogin, ]); } jsonResponse([ 'success' => true, 'message' => 'Sample updated successfully.' ]); } $sql = " INSERT INTO samples ( idcompany, idbrand, iddepartment, idproducer, idsupplier, sample_code, external_sample_id, article_no, po_no, season, style_no, style_name, model, sample_description, product_category, product_type, color, size, gender, age_group, fiber_content, material_description, claimed_weight, product_standard, production_stage, country_of_origin, status, source, created_by, created_at, updated_at ) VALUES ( :idcompany, :idbrand, :iddepartment, :idproducer, :idsupplier, :sample_code, :external_sample_id, :article_no, :po_no, :season, :style_no, :style_name, :model, :sample_description, :product_category, :product_type, :color, :size, :gender, :age_group, :fiber_content, :material_description, :claimed_weight, :product_standard, :production_stage, :country_of_origin, :status, 'manual', :created_by, NOW(), NOW() ) "; $stmt = $db->prepare($sql); $stmt->execute([ ':idcompany' => $idcompany, ':idbrand' => $idbrand, ':iddepartment' => $iddepartment, ':idproducer' => $idproducer, ':idsupplier' => $idsupplier, ':sample_code' => $sampleCode, ':external_sample_id' => $externalSampleId !== '' ? $externalSampleId : null, ':article_no' => $articleNo !== '' ? $articleNo : null, ':po_no' => $poNo !== '' ? $poNo : null, ':season' => $season !== '' ? $season : null, ':style_no' => $styleNo !== '' ? $styleNo : null, ':style_name' => $styleName !== '' ? $styleName : null, ':model' => $model !== '' ? $model : null, ':sample_description' => $sampleDescription, ':product_category' => $productCategory !== '' ? $productCategory : null, ':product_type' => $productType !== '' ? $productType : null, ':color' => $color !== '' ? $color : null, ':size' => $size !== '' ? $size : null, ':gender' => $gender !== '' ? $gender : null, ':age_group' => $ageGroup !== '' ? $ageGroup : null, ':fiber_content' => $fiberContent !== '' ? $fiberContent : null, ':material_description' => $materialDescription !== '' ? $materialDescription : null, ':claimed_weight' => $claimedWeight !== '' ? $claimedWeight : null, ':product_standard' => $productStandard !== '' ? $productStandard : null, ':production_stage' => $productionStage !== '' ? $productionStage : null, ':country_of_origin' => $countryOfOrigin, ':status' => $status, ':created_by' => $iduserlogin, ]); $newSampleId = (int) $db->lastInsertId(); $stmt = $db->prepare(" INSERT INTO sample_status_history ( idsample, old_status, new_status, note, changed_by, created_at ) VALUES ( :idsample, NULL, :new_status, :note, :changed_by, NOW() ) "); $stmt->execute([ ':idsample' => $newSampleId, ':new_status' => $status, ':note' => 'Sample created.', ':changed_by' => $iduserlogin, ]); jsonResponse([ 'success' => true, 'message' => 'Sample created successfully.' ]); } if ($action === 'get_sample') { $idsample = isset($_POST['idsample']) ? (int) $_POST['idsample'] : 0; if ($idsample <= 0) { jsonResponse([ 'success' => false, 'message' => 'Invalid sample id.' ]); } $stmt = $db->prepare(" SELECT * FROM samples WHERE idsample = :idsample LIMIT 1 "); $stmt->execute([':idsample' => $idsample]); $sample = $stmt->fetch(PDO::FETCH_ASSOC); if (!$sample) { jsonResponse([ 'success' => false, 'message' => 'Sample not found.' ]); } jsonResponse([ 'success' => true, 'sample' => $sample ]); } if ($action === 'change_sample_status') { $idsample = isset($_POST['idsample']) ? (int) $_POST['idsample'] : 0; $status = $_POST['status'] ?? 'archived'; if ($idsample <= 0 || !array_key_exists($status, $sampleStatuses)) { jsonResponse([ 'success' => false, 'message' => 'Invalid request.' ]); } $stmt = $db->prepare(" SELECT status FROM samples WHERE idsample = :idsample LIMIT 1 "); $stmt->execute([':idsample' => $idsample]); $oldStatus = $stmt->fetchColumn(); if (!$oldStatus) { jsonResponse([ 'success' => false, 'message' => 'Sample not found.' ]); } $stmt = $db->prepare(" UPDATE samples SET status = :status, updated_at = NOW() WHERE idsample = :idsample "); $stmt->execute([ ':status' => $status, ':idsample' => $idsample, ]); if ($oldStatus !== $status) { $stmt = $db->prepare(" INSERT INTO sample_status_history ( idsample, old_status, new_status, note, changed_by, created_at ) VALUES ( :idsample, :old_status, :new_status, :note, :changed_by, NOW() ) "); $stmt->execute([ ':idsample' => $idsample, ':old_status' => $oldStatus, ':new_status' => $status, ':note' => 'Status changed from sample list.', ':changed_by' => $iduserlogin, ]); } jsonResponse([ 'success' => true, 'message' => 'Sample status updated successfully.' ]); } if ($action === 'delete_sample') { $idsample = isset($_POST['idsample']) ? (int) $_POST['idsample'] : 0; if ($idsample <= 0) { jsonResponse([ 'success' => false, 'message' => 'Invalid sample id.' ]); } /* * Safe delete: * Do not delete samples with BOM parts, photos or documents. */ $stmt = $db->prepare(" SELECT (SELECT COUNT(*) FROM sample_parts WHERE idsample = :idsample1) AS parts_count, (SELECT COUNT(*) FROM sample_photos WHERE idsample = :idsample2) AS photos_count, (SELECT COUNT(*) FROM sample_documents WHERE idsample = :idsample3) AS documents_count "); $stmt->execute([ ':idsample1' => $idsample, ':idsample2' => $idsample, ':idsample3' => $idsample, ]); $usage = $stmt->fetch(PDO::FETCH_ASSOC); if ( (int) $usage['parts_count'] > 0 || (int) $usage['photos_count'] > 0 || (int) $usage['documents_count'] > 0 ) { jsonResponse([ 'success' => false, 'message' => 'This sample has BOM parts, photos or documents. Set it as archived instead of deleting it.' ]); } $stmt = $db->prepare(" DELETE FROM samples WHERE idsample = :idsample "); $stmt->execute([':idsample' => $idsample]); jsonResponse([ 'success' => true, 'message' => 'Sample deleted successfully.' ]); } jsonResponse([ 'success' => false, 'message' => 'Unknown action.' ]); } catch (Throwable $e) { jsonResponse([ 'success' => false, 'message' => $e->getMessage() ]); } } /* * Page data. */ $companies = []; $brands = []; $departments = []; $partners = []; $countries = []; $samples = []; try { $stmt = $db->query(" SELECT idcompany, company_name, status FROM companies ORDER BY company_name ASC "); $companies = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $companies = []; } try { $stmt = $db->query(" SELECT idbrand, idcompany, brand_name, status FROM brands ORDER BY brand_name ASC "); $brands = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $brands = []; } try { $stmt = $db->query(" SELECT iddepartment, idcompany, idbrand, department_name, status FROM departments ORDER BY department_name ASC "); $departments = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $departments = []; } try { $stmt = $db->query(" SELECT idpartner, idcompany, partner_type, partner_name, status FROM business_partners ORDER BY partner_name ASC "); $partners = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $partners = []; } try { $stmt = $db->query(" SELECT id, name, iso_3166_2 FROM auth_countries ORDER BY name ASC "); $countries = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $countries = []; } try { $stmt = $db->query(" SELECT s.idsample, s.idcompany, s.idbrand, s.iddepartment, s.sample_code, s.external_sample_id, s.article_no, s.po_no, s.season, s.sample_description, s.product_category, s.product_type, s.color, s.production_stage, s.status, s.source, s.created_at, c.company_name, b.brand_name, d.department_name, bp1.partner_name AS producer_name, bp2.partner_name AS supplier_name, COUNT(DISTINCT sp.idpart) AS parts_count, COUNT(DISTINCT sph.idsamplephoto) AS photos_count, COUNT(DISTINCT sd.idsampledocument) AS documents_count FROM samples s INNER JOIN companies c ON c.idcompany = s.idcompany LEFT JOIN brands b ON b.idbrand = s.idbrand LEFT JOIN departments d ON d.iddepartment = s.iddepartment LEFT JOIN business_partners bp1 ON bp1.idpartner = s.idproducer LEFT JOIN business_partners bp2 ON bp2.idpartner = s.idsupplier LEFT JOIN sample_parts sp ON sp.idsample = s.idsample LEFT JOIN sample_photos sph ON sph.idsample = s.idsample LEFT JOIN sample_documents sd ON sd.idsample = s.idsample GROUP BY s.idsample, s.idcompany, s.idbrand, s.iddepartment, s.sample_code, s.external_sample_id, s.article_no, s.po_no, s.season, s.sample_description, s.product_category, s.product_type, s.color, s.production_stage, s.status, s.source, s.created_at, c.company_name, b.brand_name, d.department_name, bp1.partner_name, bp2.partner_name ORDER BY s.created_at DESC, s.idsample DESC "); $samples = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (Throwable $e) { $samples = []; } $pageTitle = 'Samples'; $totalSamples = count($samples); $activeSamples = count(array_filter($samples, fn($row) => in_array($row['status'], ['active', 'submitted', 'under_testing'], true))); $totalParts = array_sum(array_map(fn($row) => (int) $row['parts_count'], $samples)); $totalFiles = array_sum(array_map(fn($row) => (int) $row['photos_count'] + (int) $row['documents_count'], $samples)); ?> <?= e($pageTitle); ?> - <?= isset($titlewebsite) ? e($titlewebsite) : 'TRFgo'; ?>
TRFgo Product Identity

Samples

Create and manage product/sample identity cards with company, brand, department, producer, supplier, article data, material information and testing lifecycle status.

Total Samples
Active / Testing
BOM Parts
Files
Sample List

Product identity cards ready for BOM, documents and TRF requests

No companies available. Create at least one company before adding samples.
No business partners yet. You can create samples without partners, but producer and supplier dropdowns will be empty.
Sample Company Brand / Department Producer / Supplier Article / PO BOM Files Status Actions
External ID:
Color:
-'; ?>
-'; ?>
-'; ?>
Season: