1970 lines
77 KiB
PHP
1970 lines
77 KiB
PHP
<?php include('include/headscript.php'); ?>
|
|
|
|
<?php
|
|
/*
|
|
* TRFgo - Samples management page
|
|
* This page manages the product/sample identity card.
|
|
*/
|
|
|
|
function e($value)
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function jsonResponse(array $payload): void
|
|
{
|
|
header('Content-Type: application/json');
|
|
echo json_encode($payload);
|
|
exit;
|
|
}
|
|
|
|
function generateSampleCode(PDO $db, int $idcompany): string
|
|
{
|
|
$year = date('Y');
|
|
|
|
$stmt = $db->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));
|
|
?>
|
|
|
|
<!doctype html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
|
|
<?php include('cssinclude.php'); ?>
|
|
|
|
<title><?= e($pageTitle); ?> - <?= isset($titlewebsite) ? e($titlewebsite) : 'TRFgo'; ?></title>
|
|
|
|
<style>
|
|
:root {
|
|
--trfgo-primary: #2563eb;
|
|
--trfgo-muted: #64748b;
|
|
--trfgo-border: #e5e7eb;
|
|
--trfgo-soft-blue: #eff6ff;
|
|
--trfgo-soft-green: #ecfdf5;
|
|
--trfgo-soft-orange: #fff7ed;
|
|
--trfgo-soft-purple: #f5f3ff;
|
|
}
|
|
|
|
.page-intro-card {
|
|
border: 0;
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
background:
|
|
radial-gradient(circle at top right, rgba(59, 130, 246, 0.22), transparent 30%),
|
|
linear-gradient(135deg, #0f172a 0%, #1d4ed8 60%, #38bdf8 100%);
|
|
color: #fff;
|
|
box-shadow: 0 18px 45px rgba(15, 23, 42, 0.18);
|
|
}
|
|
|
|
.page-intro-card .card-body {
|
|
padding: 26px;
|
|
}
|
|
|
|
.page-intro-card h1,
|
|
.page-intro-card h2,
|
|
.page-intro-card h3,
|
|
.page-intro-card h4,
|
|
.page-intro-card h5,
|
|
.page-intro-card h6 {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.intro-eyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 6px 12px;
|
|
border-radius: 999px;
|
|
background: rgba(255, 255, 255, 0.16);
|
|
color: rgba(255, 255, 255, 0.94);
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.intro-title {
|
|
font-size: 28px;
|
|
font-weight: 800;
|
|
letter-spacing: -0.03em;
|
|
margin-bottom: 8px;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.intro-text {
|
|
max-width: 820px;
|
|
color: rgba(255, 255, 255, 0.82);
|
|
margin-bottom: 0;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.btn-intro {
|
|
border-radius: 12px;
|
|
padding: 10px 16px;
|
|
font-weight: 700;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
border: 0;
|
|
background: #fff;
|
|
color: #1d4ed8;
|
|
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);
|
|
}
|
|
|
|
.summary-card {
|
|
border: 0;
|
|
border-radius: 18px;
|
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
|
}
|
|
|
|
.summary-icon {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 14px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 24px;
|
|
}
|
|
|
|
.summary-icon.blue {
|
|
background: var(--trfgo-soft-blue);
|
|
color: #2563eb;
|
|
}
|
|
|
|
.summary-icon.green {
|
|
background: var(--trfgo-soft-green);
|
|
color: #059669;
|
|
}
|
|
|
|
.summary-icon.orange {
|
|
background: var(--trfgo-soft-orange);
|
|
color: #ea580c;
|
|
}
|
|
|
|
.summary-icon.purple {
|
|
background: var(--trfgo-soft-purple);
|
|
color: #7c3aed;
|
|
}
|
|
|
|
.summary-label {
|
|
color: var(--trfgo-muted);
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
.summary-value {
|
|
color: #0f172a;
|
|
font-size: 26px;
|
|
font-weight: 800;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.content-card {
|
|
border: 0;
|
|
border-radius: 18px;
|
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
|
|
}
|
|
|
|
.section-title {
|
|
color: #0f172a;
|
|
font-size: 18px;
|
|
font-weight: 800;
|
|
margin-bottom: 3px;
|
|
}
|
|
|
|
.section-subtitle {
|
|
color: var(--trfgo-muted);
|
|
font-size: 13px;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.sample-table th {
|
|
color: #64748b;
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
border-bottom: 1px solid var(--trfgo-border) !important;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.sample-table td {
|
|
vertical-align: middle;
|
|
border-color: #eef2f7;
|
|
}
|
|
|
|
.sample-code {
|
|
font-weight: 800;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.sample-sub {
|
|
color: var(--trfgo-muted);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.metric-pill {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: #f8fafc;
|
|
border: 1px solid #e5e7eb;
|
|
color: #475569;
|
|
border-radius: 999px;
|
|
padding: 5px 9px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-soft-success {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
border-radius: 999px;
|
|
font-weight: 700;
|
|
padding: 6px 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-soft-warning {
|
|
background: #ffedd5;
|
|
color: #9a3412;
|
|
border-radius: 999px;
|
|
font-weight: 700;
|
|
padding: 6px 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-soft-muted {
|
|
background: #f1f5f9;
|
|
color: #475569;
|
|
border-radius: 999px;
|
|
font-weight: 700;
|
|
padding: 6px 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-soft-primary {
|
|
background: #dbeafe;
|
|
color: #1d4ed8;
|
|
border-radius: 999px;
|
|
font-weight: 700;
|
|
padding: 6px 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.badge-soft-purple {
|
|
background: #ede9fe;
|
|
color: #6d28d9;
|
|
border-radius: 999px;
|
|
font-weight: 700;
|
|
padding: 6px 10px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn-action {
|
|
width: 34px;
|
|
height: 34px;
|
|
border-radius: 11px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 1px solid #e5e7eb;
|
|
background: #fff;
|
|
color: #475569;
|
|
transition: all 0.16s ease;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.btn-action:hover {
|
|
color: #1d4ed8;
|
|
border-color: rgba(37, 99, 235, 0.35);
|
|
background: #eff6ff;
|
|
}
|
|
|
|
.btn-action.danger:hover {
|
|
color: #dc2626;
|
|
border-color: rgba(220, 38, 38, 0.30);
|
|
background: #fef2f2;
|
|
}
|
|
|
|
.modal-content {
|
|
border: 0;
|
|
border-radius: 20px;
|
|
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.22);
|
|
}
|
|
|
|
.modal-header {
|
|
border-bottom: 1px solid #eef2f7;
|
|
}
|
|
|
|
.modal-title {
|
|
font-weight: 800;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.form-label {
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
color: #334155;
|
|
}
|
|
|
|
.form-control,
|
|
.form-select {
|
|
border-radius: 12px;
|
|
border-color: #dbe3ef;
|
|
}
|
|
|
|
.form-control:focus,
|
|
.form-select:focus {
|
|
border-color: rgba(37, 99, 235, 0.45);
|
|
box-shadow: 0 0 0 0.20rem rgba(37, 99, 235, 0.12);
|
|
}
|
|
|
|
.required-dot {
|
|
color: #dc2626;
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.intro-title {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.btn-intro {
|
|
width: 100%;
|
|
justify-content: center;
|
|
margin-top: 15px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="wrapper">
|
|
<?php include('include/navbar.php'); ?>
|
|
<?php include('include/topbar.php'); ?>
|
|
|
|
<div class="page-wrapper">
|
|
<div class="page-content">
|
|
|
|
<div class="card page-intro-card mb-4">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3">
|
|
<div>
|
|
<div class="intro-eyebrow">
|
|
<i class="bx bx-package"></i>
|
|
TRFgo Product Identity
|
|
</div>
|
|
<h1 class="intro-title">Samples</h1>
|
|
<p class="intro-text">
|
|
Create and manage product/sample identity cards with company, brand, department,
|
|
producer, supplier, article data, material information and testing lifecycle status.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<button type="button" class="btn btn-intro" id="btnAddSample">
|
|
<i class="bx bx-plus-circle"></i>
|
|
Add Sample
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row row-cols-1 row-cols-md-4 mb-3">
|
|
<div class="col">
|
|
<div class="card summary-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<div class="summary-label">Total Samples</div>
|
|
<div class="summary-value"><?= e($totalSamples); ?></div>
|
|
</div>
|
|
<div class="summary-icon blue">
|
|
<i class="bx bx-package"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<div class="card summary-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<div class="summary-label">Active / Testing</div>
|
|
<div class="summary-value"><?= e($activeSamples); ?></div>
|
|
</div>
|
|
<div class="summary-icon green">
|
|
<i class="bx bx-check-circle"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<div class="card summary-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<div class="summary-label">BOM Parts</div>
|
|
<div class="summary-value"><?= e($totalParts); ?></div>
|
|
</div>
|
|
<div class="summary-icon orange">
|
|
<i class="bx bx-git-branch"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col">
|
|
<div class="card summary-card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center justify-content-between">
|
|
<div>
|
|
<div class="summary-label">Files</div>
|
|
<div class="summary-value"><?= e($totalFiles); ?></div>
|
|
</div>
|
|
<div class="summary-icon purple">
|
|
<i class="bx bx-folder"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card content-card">
|
|
<div class="card-header bg-transparent">
|
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
<div>
|
|
<h6 class="section-title mb-0">Sample List</h6>
|
|
<p class="section-subtitle">Product identity cards ready for BOM, documents and TRF requests</p>
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-primary btn-sm" id="btnAddSampleSmall">
|
|
<i class="bx bx-plus-circle"></i>
|
|
Add Sample
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<?php if (count($companies) === 0): ?>
|
|
<div class="alert alert-warning mb-3">
|
|
<strong>No companies available.</strong>
|
|
Create at least one company before adding samples.
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (count($partners) === 0): ?>
|
|
<div class="alert alert-light border mb-3">
|
|
<strong>No business partners yet.</strong>
|
|
You can create samples without partners, but producer and supplier dropdowns will be empty.
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="table-responsive">
|
|
<table id="samplesTable" class="table table-striped table-hover align-middle sample-table" style="width:100%">
|
|
<thead>
|
|
<tr>
|
|
<th>Sample</th>
|
|
<th>Company</th>
|
|
<th>Brand / Department</th>
|
|
<th>Producer / Supplier</th>
|
|
<th>Article / PO</th>
|
|
<th class="text-center">BOM</th>
|
|
<th class="text-center">Files</th>
|
|
<th>Status</th>
|
|
<th class="text-end">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<?php foreach ($samples as $sample): ?>
|
|
<?php
|
|
$filesCount = (int) $sample['photos_count'] + (int) $sample['documents_count'];
|
|
$statusLabel = $sampleStatuses[$sample['status']] ?? ucfirst($sample['status']);
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<div class="sample-code"><?= e($sample['sample_code']); ?></div>
|
|
<div class="sample-sub"><?= e($sample['sample_description']); ?></div>
|
|
<?php if (!empty($sample['external_sample_id'])): ?>
|
|
<div class="sample-sub">External ID: <?= e($sample['external_sample_id']); ?></div>
|
|
<?php endif; ?>
|
|
<?php if (!empty($sample['color'])): ?>
|
|
<div class="sample-sub">Color: <?= e($sample['color']); ?></div>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td>
|
|
<div class="sample-code"><?= e($sample['company_name']); ?></div>
|
|
<div class="sample-sub"><?= e($sampleSources[$sample['source']] ?? $sample['source']); ?></div>
|
|
</td>
|
|
|
|
<td>
|
|
<div><?= !empty($sample['brand_name']) ? e($sample['brand_name']) : '<span class="text-muted">-</span>'; ?></div>
|
|
<div class="sample-sub"><?= !empty($sample['department_name']) ? e($sample['department_name']) : '-'; ?></div>
|
|
</td>
|
|
|
|
<td>
|
|
<div><?= !empty($sample['producer_name']) ? e($sample['producer_name']) : '<span class="text-muted">-</span>'; ?></div>
|
|
<div class="sample-sub"><?= !empty($sample['supplier_name']) ? e($sample['supplier_name']) : '-'; ?></div>
|
|
</td>
|
|
|
|
<td>
|
|
<div><?= !empty($sample['article_no']) ? e($sample['article_no']) : '<span class="text-muted">-</span>'; ?></div>
|
|
<div class="sample-sub"><?= !empty($sample['po_no']) ? e($sample['po_no']) : '-'; ?></div>
|
|
<?php if (!empty($sample['season'])): ?>
|
|
<div class="sample-sub">Season: <?= e($sample['season']); ?></div>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td class="text-center">
|
|
<span class="metric-pill">
|
|
<i class="bx bx-git-branch"></i>
|
|
<?= e($sample['parts_count']); ?>
|
|
</span>
|
|
</td>
|
|
|
|
<td class="text-center">
|
|
<span class="metric-pill">
|
|
<i class="bx bx-folder"></i>
|
|
<?= e($filesCount); ?>
|
|
</span>
|
|
</td>
|
|
|
|
<td>
|
|
<?php if (in_array($sample['status'], ['active', 'submitted', 'under_testing'], true)): ?>
|
|
<span class="badge-soft-success"><?= e($statusLabel); ?></span>
|
|
<?php elseif ($sample['status'] === 'draft'): ?>
|
|
<span class="badge-soft-warning"><?= e($statusLabel); ?></span>
|
|
<?php elseif ($sample['status'] === 'completed'): ?>
|
|
<span class="badge-soft-primary"><?= e($statusLabel); ?></span>
|
|
<?php else: ?>
|
|
<span class="badge-soft-muted"><?= e($statusLabel); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td class="text-end">
|
|
<a href="sample-detail.php?idsample=<?= e($sample['idsample']); ?>"
|
|
class="btn-action"
|
|
title="Open detail">
|
|
<i class="bx bx-show"></i>
|
|
</a>
|
|
|
|
<button type="button"
|
|
class="btn-action btnEditSample"
|
|
data-id="<?= e($sample['idsample']); ?>"
|
|
title="Edit">
|
|
<i class="bx bx-edit-alt"></i>
|
|
</button>
|
|
|
|
<?php if ($sample['status'] !== 'archived'): ?>
|
|
<button type="button"
|
|
class="btn-action btnChangeSampleStatus"
|
|
data-id="<?= e($sample['idsample']); ?>"
|
|
data-status="archived"
|
|
title="Archive">
|
|
<i class="bx bx-archive"></i>
|
|
</button>
|
|
<?php else: ?>
|
|
<button type="button"
|
|
class="btn-action btnChangeSampleStatus"
|
|
data-id="<?= e($sample['idsample']); ?>"
|
|
data-status="active"
|
|
title="Set active">
|
|
<i class="bx bx-check-circle"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
|
|
<button type="button"
|
|
class="btn-action danger btnDeleteSample"
|
|
data-id="<?= e($sample['idsample']); ?>"
|
|
title="Delete">
|
|
<i class="bx bx-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overlay toggle-icon"></div>
|
|
|
|
<a href="javaScript:;" class="back-to-top">
|
|
<i class='bx bxs-up-arrow-alt'></i>
|
|
</a>
|
|
|
|
<?php include('include/footer.php'); ?>
|
|
</div>
|
|
|
|
<!-- Sample Modal -->
|
|
<div class="modal fade" id="sampleModal" tabindex="-1" aria-labelledby="sampleModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
|
<form id="sampleForm" class="modal-content">
|
|
<input type="hidden" name="action" value="save_sample">
|
|
<input type="hidden" name="idsample" id="idsample" value="0">
|
|
|
|
<div class="modal-header">
|
|
<div>
|
|
<h5 class="modal-title" id="sampleModalLabel">Add Sample</h5>
|
|
<small class="text-muted">Create or update the product/sample identity card.</small>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<div class="row g-3">
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Company <span class="required-dot">*</span></label>
|
|
<select class="form-select" name="idcompany" id="idcompany" required>
|
|
<option value="">Select company</option>
|
|
<?php foreach ($companies as $company): ?>
|
|
<option value="<?= e($company['idcompany']); ?>">
|
|
<?= e($company['company_name']); ?>
|
|
<?= $company['status'] !== 'active' ? ' - ' . e($company['status']) : ''; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Brand</label>
|
|
<select class="form-select" name="idbrand" id="idbrand">
|
|
<option value="">No brand</option>
|
|
<?php foreach ($brands as $brand): ?>
|
|
<option value="<?= e($brand['idbrand']); ?>" data-company="<?= e($brand['idcompany']); ?>">
|
|
<?= e($brand['brand_name']); ?>
|
|
<?= $brand['status'] !== 'active' ? ' - ' . e($brand['status']) : ''; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Department</label>
|
|
<select class="form-select" name="iddepartment" id="iddepartment">
|
|
<option value="">No department</option>
|
|
<?php foreach ($departments as $department): ?>
|
|
<option value="<?= e($department['iddepartment']); ?>"
|
|
data-company="<?= e($department['idcompany']); ?>"
|
|
data-brand="<?= e($department['idbrand']); ?>">
|
|
<?= e($department['department_name']); ?>
|
|
<?= $department['status'] !== 'active' ? ' - ' . e($department['status']) : ''; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Producer</label>
|
|
<select class="form-select" name="idproducer" id="idproducer">
|
|
<option value="">No producer</option>
|
|
<?php foreach ($partners as $partner): ?>
|
|
<option value="<?= e($partner['idpartner']); ?>"
|
|
data-company="<?= e($partner['idcompany']); ?>"
|
|
data-type="<?= e($partner['partner_type']); ?>">
|
|
<?= e($partner['partner_name']); ?> - <?= e($partner['partner_type']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Supplier</label>
|
|
<select class="form-select" name="idsupplier" id="idsupplier">
|
|
<option value="">No supplier</option>
|
|
<?php foreach ($partners as $partner): ?>
|
|
<option value="<?= e($partner['idpartner']); ?>"
|
|
data-company="<?= e($partner['idcompany']); ?>"
|
|
data-type="<?= e($partner['partner_type']); ?>">
|
|
<?= e($partner['partner_name']); ?> - <?= e($partner['partner_type']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" name="status" id="status">
|
|
<?php foreach ($sampleStatuses as $value => $label): ?>
|
|
<option value="<?= e($value); ?>"><?= e($label); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Sample Code</label>
|
|
<input type="text" class="form-control" name="sample_code" id="sample_code" placeholder="Auto if empty">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">External Sample ID</label>
|
|
<input type="text" class="form-control" name="external_sample_id" id="external_sample_id">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Article No.</label>
|
|
<input type="text" class="form-control" name="article_no" id="article_no">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">PO No.</label>
|
|
<input type="text" class="form-control" name="po_no" id="po_no">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Season</label>
|
|
<input type="text" class="form-control" name="season" id="season">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Production Stage</label>
|
|
<input type="text" class="form-control" name="production_stage" id="production_stage">
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Sample Description <span class="required-dot">*</span></label>
|
|
<input type="text" class="form-control" name="sample_description" id="sample_description" required>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Style No.</label>
|
|
<input type="text" class="form-control" name="style_no" id="style_no">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Style Name</label>
|
|
<input type="text" class="form-control" name="style_name" id="style_name">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Model</label>
|
|
<input type="text" class="form-control" name="model" id="model">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Product Category</label>
|
|
<input type="text" class="form-control" name="product_category" id="product_category">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Product Type</label>
|
|
<input type="text" class="form-control" name="product_type" id="product_type">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Color</label>
|
|
<input type="text" class="form-control" name="color" id="color">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Size</label>
|
|
<input type="text" class="form-control" name="size" id="size">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Gender</label>
|
|
<input type="text" class="form-control" name="gender" id="gender">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Age Group</label>
|
|
<input type="text" class="form-control" name="age_group" id="age_group">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Claimed Weight</label>
|
|
<input type="text" class="form-control" name="claimed_weight" id="claimed_weight">
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Fiber Content</label>
|
|
<textarea class="form-control" name="fiber_content" id="fiber_content" rows="2"></textarea>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Material Description</label>
|
|
<textarea class="form-control" name="material_description" id="material_description" rows="2"></textarea>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Product Standard</label>
|
|
<input type="text" class="form-control" name="product_standard" id="product_standard">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Country of Origin</label>
|
|
<select class="form-select" name="country_of_origin" id="country_of_origin">
|
|
<option value="">Select country</option>
|
|
<?php foreach ($countries as $country): ?>
|
|
<option value="<?= e($country['id']); ?>">
|
|
<?= e($country['name']); ?>
|
|
<?= !empty($country['iso_3166_2']) ? ' (' . e($country['iso_3166_2']) . ')' : ''; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary" id="btnSaveSample">
|
|
<i class="bx bx-save"></i>
|
|
Save Sample
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include('jsinclude.php'); ?>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
if ($.fn.DataTable) {
|
|
$('#samplesTable').DataTable({
|
|
pageLength: 25,
|
|
order: [
|
|
[0, 'desc']
|
|
],
|
|
responsive: true,
|
|
language: {
|
|
search: "",
|
|
searchPlaceholder: "Search samples..."
|
|
}
|
|
});
|
|
}
|
|
|
|
const sampleModal = new bootstrap.Modal(document.getElementById('sampleModal'));
|
|
|
|
function showAlert(type, message) {
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: type,
|
|
title: type === 'success' ? 'Done' : 'Attention',
|
|
text: message,
|
|
confirmButtonColor: '#2563eb'
|
|
});
|
|
return;
|
|
}
|
|
|
|
alert(message);
|
|
}
|
|
|
|
function reloadAfterSuccess(message) {
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Done',
|
|
text: message,
|
|
confirmButtonColor: '#2563eb'
|
|
}).then(function() {
|
|
window.location.reload();
|
|
});
|
|
return;
|
|
}
|
|
|
|
alert(message);
|
|
window.location.reload();
|
|
}
|
|
|
|
function resetSampleForm() {
|
|
$('#sampleForm')[0].reset();
|
|
$('#idsample').val('0');
|
|
$('#sampleModalLabel').text('Add Sample');
|
|
$('#status').val('draft');
|
|
filterBrandOptions('');
|
|
filterDepartmentOptions('', '');
|
|
filterPartnerOptions('');
|
|
}
|
|
|
|
function filterBrandOptions(idcompany, selectedBrandId = '') {
|
|
$('#idbrand option').each(function() {
|
|
const optionCompany = $(this).data('company');
|
|
|
|
if ($(this).val() === '') {
|
|
$(this).show();
|
|
return;
|
|
}
|
|
|
|
if (!idcompany || String(optionCompany) === String(idcompany)) {
|
|
$(this).show();
|
|
} else {
|
|
$(this).hide();
|
|
}
|
|
});
|
|
|
|
$('#idbrand').val(selectedBrandId || '');
|
|
}
|
|
|
|
function filterDepartmentOptions(idcompany, idbrand, selectedDepartmentId = '') {
|
|
$('#iddepartment option').each(function() {
|
|
const optionCompany = $(this).data('company');
|
|
const optionBrand = $(this).data('brand');
|
|
|
|
if ($(this).val() === '') {
|
|
$(this).show();
|
|
return;
|
|
}
|
|
|
|
if (!idcompany) {
|
|
$(this).hide();
|
|
return;
|
|
}
|
|
|
|
if (String(optionCompany) !== String(idcompany)) {
|
|
$(this).hide();
|
|
return;
|
|
}
|
|
|
|
if (!idbrand || String(optionBrand) === String(idbrand) || optionBrand === '' || typeof optionBrand === 'undefined') {
|
|
$(this).show();
|
|
} else {
|
|
$(this).hide();
|
|
}
|
|
});
|
|
|
|
$('#iddepartment').val(selectedDepartmentId || '');
|
|
}
|
|
|
|
function filterPartnerOptions(idcompany, selectedProducerId = '', selectedSupplierId = '') {
|
|
$('#idproducer option, #idsupplier option').each(function() {
|
|
const optionCompany = $(this).data('company');
|
|
|
|
if ($(this).val() === '') {
|
|
$(this).show();
|
|
return;
|
|
}
|
|
|
|
if (!idcompany || String(optionCompany) === String(idcompany)) {
|
|
$(this).show();
|
|
} else {
|
|
$(this).hide();
|
|
}
|
|
});
|
|
|
|
$('#idproducer').val(selectedProducerId || '');
|
|
$('#idsupplier').val(selectedSupplierId || '');
|
|
}
|
|
|
|
$('#idcompany').on('change', function() {
|
|
const idcompany = $(this).val();
|
|
|
|
filterBrandOptions(idcompany);
|
|
filterDepartmentOptions(idcompany, '');
|
|
filterPartnerOptions(idcompany);
|
|
});
|
|
|
|
$('#idbrand').on('change', function() {
|
|
const idcompany = $('#idcompany').val();
|
|
const idbrand = $(this).val();
|
|
|
|
filterDepartmentOptions(idcompany, idbrand);
|
|
});
|
|
|
|
$('#btnAddSample, #btnAddSampleSmall').on('click', function() {
|
|
resetSampleForm();
|
|
sampleModal.show();
|
|
});
|
|
|
|
$('.btnEditSample').on('click', function() {
|
|
const idsample = $(this).data('id');
|
|
|
|
$.ajax({
|
|
url: 'samples.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'get_sample',
|
|
idsample: idsample
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to load sample.');
|
|
return;
|
|
}
|
|
|
|
const sample = response.sample;
|
|
|
|
$('#sampleModalLabel').text('Edit Sample');
|
|
$('#idsample').val(sample.idsample);
|
|
$('#idcompany').val(sample.idcompany);
|
|
|
|
filterBrandOptions(sample.idcompany, sample.idbrand);
|
|
filterDepartmentOptions(sample.idcompany, sample.idbrand, sample.iddepartment);
|
|
filterPartnerOptions(sample.idcompany, sample.idproducer, sample.idsupplier);
|
|
|
|
$('#sample_code').val(sample.sample_code);
|
|
$('#external_sample_id').val(sample.external_sample_id);
|
|
$('#article_no').val(sample.article_no);
|
|
$('#po_no').val(sample.po_no);
|
|
$('#season').val(sample.season);
|
|
$('#style_no').val(sample.style_no);
|
|
$('#style_name').val(sample.style_name);
|
|
$('#model').val(sample.model);
|
|
$('#sample_description').val(sample.sample_description);
|
|
$('#product_category').val(sample.product_category);
|
|
$('#product_type').val(sample.product_type);
|
|
$('#color').val(sample.color);
|
|
$('#size').val(sample.size);
|
|
$('#gender').val(sample.gender);
|
|
$('#age_group').val(sample.age_group);
|
|
$('#fiber_content').val(sample.fiber_content);
|
|
$('#material_description').val(sample.material_description);
|
|
$('#claimed_weight').val(sample.claimed_weight);
|
|
$('#product_standard').val(sample.product_standard);
|
|
$('#production_stage').val(sample.production_stage);
|
|
$('#country_of_origin').val(sample.country_of_origin);
|
|
$('#status').val(sample.status);
|
|
|
|
sampleModal.show();
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while loading sample.');
|
|
}
|
|
});
|
|
});
|
|
|
|
$('#sampleForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
$('#btnSaveSample').prop('disabled', true).html('<i class="bx bx-loader-alt bx-spin"></i> Saving...');
|
|
|
|
$.ajax({
|
|
url: 'samples.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: $(this).serialize(),
|
|
success: function(response) {
|
|
$('#btnSaveSample').prop('disabled', false).html('<i class="bx bx-save"></i> Save Sample');
|
|
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to save sample.');
|
|
return;
|
|
}
|
|
|
|
sampleModal.hide();
|
|
reloadAfterSuccess(response.message || 'Sample saved successfully.');
|
|
},
|
|
error: function() {
|
|
$('#btnSaveSample').prop('disabled', false).html('<i class="bx bx-save"></i> Save Sample');
|
|
showAlert('error', 'Server error while saving sample.');
|
|
}
|
|
});
|
|
});
|
|
|
|
$('.btnChangeSampleStatus').on('click', function() {
|
|
const idsample = $(this).data('id');
|
|
const status = $(this).data('status');
|
|
|
|
const message = status === 'archived' ?
|
|
'Do you want to archive this sample?' :
|
|
'Do you want to set this sample as active?';
|
|
|
|
const doChange = function() {
|
|
$.ajax({
|
|
url: 'samples.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'change_sample_status',
|
|
idsample: idsample,
|
|
status: status
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to update status.');
|
|
return;
|
|
}
|
|
|
|
reloadAfterSuccess(response.message || 'Status updated successfully.');
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while updating status.');
|
|
}
|
|
});
|
|
};
|
|
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'question',
|
|
title: 'Confirm status change',
|
|
text: message,
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#2563eb',
|
|
cancelButtonColor: '#64748b',
|
|
confirmButtonText: 'Yes, continue'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
doChange();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (confirm(message)) {
|
|
doChange();
|
|
}
|
|
});
|
|
|
|
$('.btnDeleteSample').on('click', function() {
|
|
const idsample = $(this).data('id');
|
|
|
|
const doDelete = function() {
|
|
$.ajax({
|
|
url: 'samples.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'delete_sample',
|
|
idsample: idsample
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to delete sample.');
|
|
return;
|
|
}
|
|
|
|
reloadAfterSuccess(response.message || 'Sample deleted successfully.');
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while deleting sample.');
|
|
}
|
|
});
|
|
};
|
|
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Delete sample?',
|
|
text: 'The sample will be deleted only if it has no BOM parts, photos or documents.',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#dc2626',
|
|
cancelButtonColor: '#64748b',
|
|
confirmButtonText: 'Yes, delete'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
doDelete();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (confirm('Delete sample?')) {
|
|
doDelete();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|