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.
Add Sample
Total Samples
= e($totalSamples); ?>
Active / Testing
= e($activeSamples); ?>
BOM Parts
= e($totalParts); ?>
Files
= e($totalFiles); ?>
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
= e($sample['sample_code']); ?>
= e($sample['sample_description']); ?>
External ID: = e($sample['external_sample_id']); ?>
Color: = e($sample['color']); ?>
= e($sample['company_name']); ?>
= e($sampleSources[$sample['source']] ?? $sample['source']); ?>
= !empty($sample['brand_name']) ? e($sample['brand_name']) : '- '; ?>
= !empty($sample['department_name']) ? e($sample['department_name']) : '-'; ?>
= !empty($sample['producer_name']) ? e($sample['producer_name']) : '- '; ?>
= !empty($sample['supplier_name']) ? e($sample['supplier_name']) : '-'; ?>
= !empty($sample['article_no']) ? e($sample['article_no']) : '- '; ?>
= !empty($sample['po_no']) ? e($sample['po_no']) : '-'; ?>
Season: = e($sample['season']); ?>
= e($sample['parts_count']); ?>
= e($filesCount); ?>
= e($statusLabel); ?>
= e($statusLabel); ?>
= e($statusLabel); ?>
= e($statusLabel); ?>