1988 lines
76 KiB
PHP
1988 lines
76 KiB
PHP
<?php include('include/headscript.php'); ?>
|
|
|
|
<?php
|
|
/*
|
|
* TRFgo - Business Partners management page
|
|
* This page manages suppliers, producers, manufacturers, vendors, factories and related contacts.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
$partnerTypes = [
|
|
'producer' => 'Producer',
|
|
'manufacturer' => 'Manufacturer',
|
|
'supplier' => 'Supplier',
|
|
'vendor' => 'Vendor',
|
|
'factory' => 'Factory',
|
|
'agent' => 'Agent',
|
|
'invoice_to' => 'Invoice To',
|
|
'report_to' => 'Report To',
|
|
'laboratory' => 'Laboratory',
|
|
'other' => 'Other',
|
|
];
|
|
|
|
$partnerStatuses = [
|
|
'active' => 'Active',
|
|
'inactive' => 'Inactive',
|
|
'suspended' => 'Suspended',
|
|
];
|
|
|
|
$contactStatuses = [
|
|
'active' => 'Active',
|
|
'inactive' => 'Inactive',
|
|
];
|
|
|
|
/*
|
|
* AJAX actions
|
|
*/
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
$action = $_POST['action'];
|
|
|
|
try {
|
|
if ($action === 'save_partner') {
|
|
$idpartner = isset($_POST['idpartner']) ? (int) $_POST['idpartner'] : 0;
|
|
$idcompany = isset($_POST['idcompany']) ? (int) $_POST['idcompany'] : 0;
|
|
|
|
$partnerType = $_POST['partner_type'] ?? 'supplier';
|
|
$partnerName = trim($_POST['partner_name'] ?? '');
|
|
$legalName = trim($_POST['legal_name'] ?? '');
|
|
$externalCode = trim($_POST['external_code'] ?? '');
|
|
$vatNumber = trim($_POST['vat_number'] ?? '');
|
|
$taxCode = trim($_POST['tax_code'] ?? '');
|
|
$address = trim($_POST['address'] ?? '');
|
|
$city = trim($_POST['city'] ?? '');
|
|
$zip = trim($_POST['zip'] ?? '');
|
|
$countryId = !empty($_POST['country_id']) ? (int) $_POST['country_id'] : null;
|
|
$email = trim($_POST['email'] ?? '');
|
|
$phone = trim($_POST['phone'] ?? '');
|
|
$website = trim($_POST['website'] ?? '');
|
|
$notes = trim($_POST['notes'] ?? '');
|
|
$status = $_POST['status'] ?? 'active';
|
|
|
|
if ($idcompany <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Company is required.'
|
|
]);
|
|
}
|
|
|
|
if ($partnerName === '') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Partner name is required.'
|
|
]);
|
|
}
|
|
|
|
if (!array_key_exists($partnerType, $partnerTypes)) {
|
|
$partnerType = 'supplier';
|
|
}
|
|
|
|
if (!array_key_exists($status, $partnerStatuses)) {
|
|
$status = 'active';
|
|
}
|
|
|
|
/*
|
|
* 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.'
|
|
]);
|
|
}
|
|
|
|
if ($idpartner > 0) {
|
|
$sql = "
|
|
UPDATE business_partners
|
|
SET
|
|
idcompany = :idcompany,
|
|
partner_type = :partner_type,
|
|
partner_name = :partner_name,
|
|
legal_name = :legal_name,
|
|
external_code = :external_code,
|
|
vat_number = :vat_number,
|
|
tax_code = :tax_code,
|
|
address = :address,
|
|
city = :city,
|
|
zip = :zip,
|
|
country_id = :country_id,
|
|
email = :email,
|
|
phone = :phone,
|
|
website = :website,
|
|
notes = :notes,
|
|
status = :status,
|
|
updated_at = NOW()
|
|
WHERE idpartner = :idpartner
|
|
";
|
|
|
|
$stmt = $db->prepare($sql);
|
|
$stmt->execute([
|
|
':idcompany' => $idcompany,
|
|
':partner_type' => $partnerType,
|
|
':partner_name' => $partnerName,
|
|
':legal_name' => $legalName !== '' ? $legalName : null,
|
|
':external_code' => $externalCode !== '' ? $externalCode : null,
|
|
':vat_number' => $vatNumber !== '' ? $vatNumber : null,
|
|
':tax_code' => $taxCode !== '' ? $taxCode : null,
|
|
':address' => $address !== '' ? $address : null,
|
|
':city' => $city !== '' ? $city : null,
|
|
':zip' => $zip !== '' ? $zip : null,
|
|
':country_id' => $countryId,
|
|
':email' => $email !== '' ? $email : null,
|
|
':phone' => $phone !== '' ? $phone : null,
|
|
':website' => $website !== '' ? $website : null,
|
|
':notes' => $notes !== '' ? $notes : null,
|
|
':status' => $status,
|
|
':idpartner' => $idpartner,
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Business partner updated successfully.'
|
|
]);
|
|
}
|
|
|
|
$sql = "
|
|
INSERT INTO business_partners (
|
|
idcompany,
|
|
partner_type,
|
|
partner_name,
|
|
legal_name,
|
|
external_code,
|
|
vat_number,
|
|
tax_code,
|
|
address,
|
|
city,
|
|
zip,
|
|
country_id,
|
|
email,
|
|
phone,
|
|
website,
|
|
notes,
|
|
status,
|
|
created_by,
|
|
created_at,
|
|
updated_at
|
|
) VALUES (
|
|
:idcompany,
|
|
:partner_type,
|
|
:partner_name,
|
|
:legal_name,
|
|
:external_code,
|
|
:vat_number,
|
|
:tax_code,
|
|
:address,
|
|
:city,
|
|
:zip,
|
|
:country_id,
|
|
:email,
|
|
:phone,
|
|
:website,
|
|
:notes,
|
|
:status,
|
|
:created_by,
|
|
NOW(),
|
|
NOW()
|
|
)
|
|
";
|
|
|
|
$stmt = $db->prepare($sql);
|
|
$stmt->execute([
|
|
':idcompany' => $idcompany,
|
|
':partner_type' => $partnerType,
|
|
':partner_name' => $partnerName,
|
|
':legal_name' => $legalName !== '' ? $legalName : null,
|
|
':external_code' => $externalCode !== '' ? $externalCode : null,
|
|
':vat_number' => $vatNumber !== '' ? $vatNumber : null,
|
|
':tax_code' => $taxCode !== '' ? $taxCode : null,
|
|
':address' => $address !== '' ? $address : null,
|
|
':city' => $city !== '' ? $city : null,
|
|
':zip' => $zip !== '' ? $zip : null,
|
|
':country_id' => $countryId,
|
|
':email' => $email !== '' ? $email : null,
|
|
':phone' => $phone !== '' ? $phone : null,
|
|
':website' => $website !== '' ? $website : null,
|
|
':notes' => $notes !== '' ? $notes : null,
|
|
':status' => $status,
|
|
':created_by' => $iduserlogin,
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Business partner created successfully.'
|
|
]);
|
|
}
|
|
|
|
if ($action === 'get_partner') {
|
|
$idpartner = isset($_POST['idpartner']) ? (int) $_POST['idpartner'] : 0;
|
|
|
|
if ($idpartner <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Invalid partner id.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
SELECT *
|
|
FROM business_partners
|
|
WHERE idpartner = :idpartner
|
|
LIMIT 1
|
|
");
|
|
$stmt->execute([':idpartner' => $idpartner]);
|
|
$partner = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$partner) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Business partner not found.'
|
|
]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'partner' => $partner
|
|
]);
|
|
}
|
|
|
|
if ($action === 'change_partner_status') {
|
|
$idpartner = isset($_POST['idpartner']) ? (int) $_POST['idpartner'] : 0;
|
|
$status = $_POST['status'] ?? 'inactive';
|
|
|
|
if ($idpartner <= 0 || !array_key_exists($status, $partnerStatuses)) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Invalid request.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
UPDATE business_partners
|
|
SET status = :status, updated_at = NOW()
|
|
WHERE idpartner = :idpartner
|
|
");
|
|
$stmt->execute([
|
|
':status' => $status,
|
|
':idpartner' => $idpartner,
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Business partner status updated successfully.'
|
|
]);
|
|
}
|
|
|
|
if ($action === 'delete_partner') {
|
|
$idpartner = isset($_POST['idpartner']) ? (int) $_POST['idpartner'] : 0;
|
|
|
|
if ($idpartner <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Invalid partner id.'
|
|
]);
|
|
}
|
|
|
|
/*
|
|
* Safe delete:
|
|
* Do not delete a partner if already used by samples or parts.
|
|
*/
|
|
$stmt = $db->prepare("
|
|
SELECT
|
|
(SELECT COUNT(*) FROM samples WHERE idproducer = :idpartner1 OR idsupplier = :idpartner2) AS samples_count,
|
|
(SELECT COUNT(*) FROM sample_parts WHERE supplier_id = :idpartner3 OR producer_id = :idpartner4) AS parts_count
|
|
");
|
|
$stmt->execute([
|
|
':idpartner1' => $idpartner,
|
|
':idpartner2' => $idpartner,
|
|
':idpartner3' => $idpartner,
|
|
':idpartner4' => $idpartner,
|
|
]);
|
|
|
|
$usage = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (((int) $usage['samples_count'] > 0) || ((int) $usage['parts_count'] > 0)) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'This partner is linked to samples or BOM parts. Set it as inactive instead of deleting it.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
DELETE FROM business_partners
|
|
WHERE idpartner = :idpartner
|
|
");
|
|
$stmt->execute([':idpartner' => $idpartner]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Business partner deleted successfully.'
|
|
]);
|
|
}
|
|
|
|
if ($action === 'get_partner_contacts') {
|
|
$idpartner = isset($_POST['idpartner']) ? (int) $_POST['idpartner'] : 0;
|
|
|
|
if ($idpartner <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Invalid partner id.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
SELECT *
|
|
FROM business_partner_contacts
|
|
WHERE idpartner = :idpartner
|
|
ORDER BY is_primary DESC, contact_name ASC
|
|
");
|
|
$stmt->execute([':idpartner' => $idpartner]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'contacts' => $stmt->fetchAll(PDO::FETCH_ASSOC)
|
|
]);
|
|
}
|
|
|
|
if ($action === 'save_contact') {
|
|
$idcontact = isset($_POST['idcontact']) ? (int) $_POST['idcontact'] : 0;
|
|
$idpartner = isset($_POST['idpartner']) ? (int) $_POST['idpartner'] : 0;
|
|
$contactName = trim($_POST['contact_name'] ?? '');
|
|
$role = trim($_POST['role'] ?? '');
|
|
$email = trim($_POST['email'] ?? '');
|
|
$phone = trim($_POST['phone'] ?? '');
|
|
$mobile = trim($_POST['mobile'] ?? '');
|
|
$isPrimary = isset($_POST['is_primary']) ? (int) $_POST['is_primary'] : 0;
|
|
$notes = trim($_POST['notes'] ?? '');
|
|
$status = $_POST['status'] ?? 'active';
|
|
|
|
if ($idpartner <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Partner is required.'
|
|
]);
|
|
}
|
|
|
|
if ($contactName === '') {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Contact name is required.'
|
|
]);
|
|
}
|
|
|
|
if (!array_key_exists($status, $contactStatuses)) {
|
|
$status = 'active';
|
|
}
|
|
|
|
/*
|
|
* If this contact is primary, unset other primary contacts for the same partner.
|
|
*/
|
|
if ($isPrimary === 1) {
|
|
$stmt = $db->prepare("
|
|
UPDATE business_partner_contacts
|
|
SET is_primary = 0
|
|
WHERE idpartner = :idpartner
|
|
");
|
|
$stmt->execute([':idpartner' => $idpartner]);
|
|
}
|
|
|
|
if ($idcontact > 0) {
|
|
$stmt = $db->prepare("
|
|
UPDATE business_partner_contacts
|
|
SET
|
|
contact_name = :contact_name,
|
|
role = :role,
|
|
email = :email,
|
|
phone = :phone,
|
|
mobile = :mobile,
|
|
is_primary = :is_primary,
|
|
notes = :notes,
|
|
status = :status,
|
|
updated_at = NOW()
|
|
WHERE idcontact = :idcontact
|
|
AND idpartner = :idpartner
|
|
");
|
|
|
|
$stmt->execute([
|
|
':contact_name' => $contactName,
|
|
':role' => $role !== '' ? $role : null,
|
|
':email' => $email !== '' ? $email : null,
|
|
':phone' => $phone !== '' ? $phone : null,
|
|
':mobile' => $mobile !== '' ? $mobile : null,
|
|
':is_primary' => $isPrimary === 1 ? 1 : 0,
|
|
':notes' => $notes !== '' ? $notes : null,
|
|
':status' => $status,
|
|
':idcontact' => $idcontact,
|
|
':idpartner' => $idpartner,
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Contact updated successfully.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
INSERT INTO business_partner_contacts (
|
|
idpartner,
|
|
contact_name,
|
|
role,
|
|
email,
|
|
phone,
|
|
mobile,
|
|
is_primary,
|
|
notes,
|
|
status,
|
|
created_at,
|
|
updated_at
|
|
) VALUES (
|
|
:idpartner,
|
|
:contact_name,
|
|
:role,
|
|
:email,
|
|
:phone,
|
|
:mobile,
|
|
:is_primary,
|
|
:notes,
|
|
:status,
|
|
NOW(),
|
|
NOW()
|
|
)
|
|
");
|
|
|
|
$stmt->execute([
|
|
':idpartner' => $idpartner,
|
|
':contact_name' => $contactName,
|
|
':role' => $role !== '' ? $role : null,
|
|
':email' => $email !== '' ? $email : null,
|
|
':phone' => $phone !== '' ? $phone : null,
|
|
':mobile' => $mobile !== '' ? $mobile : null,
|
|
':is_primary' => $isPrimary === 1 ? 1 : 0,
|
|
':notes' => $notes !== '' ? $notes : null,
|
|
':status' => $status,
|
|
]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Contact created successfully.'
|
|
]);
|
|
}
|
|
|
|
if ($action === 'get_contact') {
|
|
$idcontact = isset($_POST['idcontact']) ? (int) $_POST['idcontact'] : 0;
|
|
|
|
if ($idcontact <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Invalid contact id.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
SELECT *
|
|
FROM business_partner_contacts
|
|
WHERE idcontact = :idcontact
|
|
LIMIT 1
|
|
");
|
|
$stmt->execute([':idcontact' => $idcontact]);
|
|
$contact = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$contact) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Contact not found.'
|
|
]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'contact' => $contact
|
|
]);
|
|
}
|
|
|
|
if ($action === 'delete_contact') {
|
|
$idcontact = isset($_POST['idcontact']) ? (int) $_POST['idcontact'] : 0;
|
|
|
|
if ($idcontact <= 0) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Invalid contact id.'
|
|
]);
|
|
}
|
|
|
|
$stmt = $db->prepare("
|
|
DELETE FROM business_partner_contacts
|
|
WHERE idcontact = :idcontact
|
|
");
|
|
$stmt->execute([':idcontact' => $idcontact]);
|
|
|
|
jsonResponse([
|
|
'success' => true,
|
|
'message' => 'Contact deleted successfully.'
|
|
]);
|
|
}
|
|
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => 'Unknown action.'
|
|
]);
|
|
} catch (Throwable $e) {
|
|
jsonResponse([
|
|
'success' => false,
|
|
'message' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Page data.
|
|
*/
|
|
$companies = [];
|
|
$countries = [];
|
|
$partners = [];
|
|
|
|
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 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
|
|
bp.idpartner,
|
|
bp.idcompany,
|
|
bp.partner_type,
|
|
bp.partner_name,
|
|
bp.legal_name,
|
|
bp.external_code,
|
|
bp.vat_number,
|
|
bp.city,
|
|
bp.email,
|
|
bp.phone,
|
|
bp.status,
|
|
bp.created_at,
|
|
c.company_name,
|
|
ac.name AS country_name,
|
|
COUNT(DISTINCT bpc.idcontact) AS contacts_count,
|
|
SUM(CASE WHEN bpc.is_primary = 1 THEN 1 ELSE 0 END) AS primary_contacts_count,
|
|
COUNT(DISTINCT s1.idsample) AS producer_samples_count,
|
|
COUNT(DISTINCT s2.idsample) AS supplier_samples_count,
|
|
COUNT(DISTINCT sp1.idpart) AS producer_parts_count,
|
|
COUNT(DISTINCT sp2.idpart) AS supplier_parts_count
|
|
FROM business_partners bp
|
|
INNER JOIN companies c ON c.idcompany = bp.idcompany
|
|
LEFT JOIN auth_countries ac ON ac.id = bp.country_id
|
|
LEFT JOIN business_partner_contacts bpc ON bpc.idpartner = bp.idpartner
|
|
LEFT JOIN samples s1 ON s1.idproducer = bp.idpartner
|
|
LEFT JOIN samples s2 ON s2.idsupplier = bp.idpartner
|
|
LEFT JOIN sample_parts sp1 ON sp1.producer_id = bp.idpartner
|
|
LEFT JOIN sample_parts sp2 ON sp2.supplier_id = bp.idpartner
|
|
GROUP BY
|
|
bp.idpartner,
|
|
bp.idcompany,
|
|
bp.partner_type,
|
|
bp.partner_name,
|
|
bp.legal_name,
|
|
bp.external_code,
|
|
bp.vat_number,
|
|
bp.city,
|
|
bp.email,
|
|
bp.phone,
|
|
bp.status,
|
|
bp.created_at,
|
|
c.company_name,
|
|
ac.name
|
|
ORDER BY c.company_name ASC, bp.partner_name ASC
|
|
");
|
|
$partners = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (Throwable $e) {
|
|
$partners = [];
|
|
}
|
|
|
|
$pageTitle = 'Business Partners';
|
|
$totalPartners = count($partners);
|
|
$activePartners = count(array_filter($partners, fn($row) => $row['status'] === 'active'));
|
|
$totalContacts = array_sum(array_map(fn($row) => (int) $row['contacts_count'], $partners));
|
|
?>
|
|
|
|
<!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: 780px;
|
|
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-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;
|
|
}
|
|
|
|
.partner-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;
|
|
}
|
|
|
|
.partner-table td {
|
|
vertical-align: middle;
|
|
border-color: #eef2f7;
|
|
}
|
|
|
|
.partner-name {
|
|
font-weight: 800;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.partner-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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.contact-list-wrapper {
|
|
border: 1px solid #eef2f7;
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.contact-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 14px;
|
|
padding: 13px 15px;
|
|
border-bottom: 1px solid #eef2f7;
|
|
}
|
|
|
|
.contact-row:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.contact-name {
|
|
font-weight: 800;
|
|
color: #0f172a;
|
|
}
|
|
|
|
.contact-sub {
|
|
color: var(--trfgo-muted);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 30px 18px;
|
|
color: var(--trfgo-muted);
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 42px;
|
|
color: #cbd5e1;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.intro-title {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.btn-intro {
|
|
width: 100%;
|
|
justify-content: center;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.contact-row {
|
|
align-items: flex-start;
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</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-network-chart"></i>
|
|
TRFgo Master Data
|
|
</div>
|
|
<h1 class="intro-title">Business Partners</h1>
|
|
<p class="intro-text">
|
|
Manage producers, suppliers, manufacturers, vendors, factories, laboratories and contacts.
|
|
These records will be used in sample identity cards, BOM parts, TRF requests and document flows.
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<button type="button" class="btn btn-intro" id="btnAddPartner">
|
|
<i class="bx bx-plus-circle"></i>
|
|
Add Partner
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row row-cols-1 row-cols-md-3 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 Partners</div>
|
|
<div class="summary-value"><?= e($totalPartners); ?></div>
|
|
</div>
|
|
<div class="summary-icon blue">
|
|
<i class="bx bx-network-chart"></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 Partners</div>
|
|
<div class="summary-value"><?= e($activePartners); ?></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">Contacts</div>
|
|
<div class="summary-value"><?= e($totalContacts); ?></div>
|
|
</div>
|
|
<div class="summary-icon orange">
|
|
<i class="bx bx-user-pin"></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">Partner List</h6>
|
|
<p class="section-subtitle">Suppliers, producers, vendors, laboratories and related entities</p>
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-primary btn-sm" id="btnAddPartnerSmall">
|
|
<i class="bx bx-plus-circle"></i>
|
|
Add Partner
|
|
</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 business partners.
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="table-responsive">
|
|
<table id="partnersTable" class="table table-striped table-hover align-middle partner-table" style="width:100%">
|
|
<thead>
|
|
<tr>
|
|
<th>Partner</th>
|
|
<th>Company</th>
|
|
<th>Type</th>
|
|
<th>External Code</th>
|
|
<th>Country / City</th>
|
|
<th>Email</th>
|
|
<th class="text-center">Contacts</th>
|
|
<th class="text-center">Usage</th>
|
|
<th>Status</th>
|
|
<th class="text-end">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<?php foreach ($partners as $partner): ?>
|
|
<?php
|
|
$usageCount =
|
|
(int) $partner['producer_samples_count'] +
|
|
(int) $partner['supplier_samples_count'] +
|
|
(int) $partner['producer_parts_count'] +
|
|
(int) $partner['supplier_parts_count'];
|
|
|
|
$partnerTypeLabel = $partnerTypes[$partner['partner_type']] ?? ucfirst($partner['partner_type']);
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<div class="partner-name"><?= e($partner['partner_name']); ?></div>
|
|
<?php if (!empty($partner['legal_name'])): ?>
|
|
<div class="partner-sub"><?= e($partner['legal_name']); ?></div>
|
|
<?php endif; ?>
|
|
<?php if (!empty($partner['vat_number'])): ?>
|
|
<div class="partner-sub">VAT: <?= e($partner['vat_number']); ?></div>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td>
|
|
<div class="partner-name"><?= e($partner['company_name']); ?></div>
|
|
</td>
|
|
|
|
<td>
|
|
<span class="badge-soft-primary"><?= e($partnerTypeLabel); ?></span>
|
|
</td>
|
|
|
|
<td>
|
|
<?= !empty($partner['external_code']) ? e($partner['external_code']) : '<span class="text-muted">-</span>'; ?>
|
|
</td>
|
|
|
|
<td>
|
|
<div><?= !empty($partner['country_name']) ? e($partner['country_name']) : '<span class="text-muted">-</span>'; ?></div>
|
|
<?php if (!empty($partner['city'])): ?>
|
|
<div class="partner-sub"><?= e($partner['city']); ?></div>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td>
|
|
<?php if (!empty($partner['email'])): ?>
|
|
<a href="mailto:<?= e($partner['email']); ?>"><?= e($partner['email']); ?></a>
|
|
<?php else: ?>
|
|
<span class="text-muted">-</span>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($partner['phone'])): ?>
|
|
<div class="partner-sub"><?= e($partner['phone']); ?></div>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td class="text-center">
|
|
<span class="metric-pill">
|
|
<i class="bx bx-user-pin"></i>
|
|
<?= e($partner['contacts_count']); ?>
|
|
</span>
|
|
</td>
|
|
|
|
<td class="text-center">
|
|
<span class="metric-pill">
|
|
<i class="bx bx-link"></i>
|
|
<?= e($usageCount); ?>
|
|
</span>
|
|
</td>
|
|
|
|
<td>
|
|
<?php if ($partner['status'] === 'active'): ?>
|
|
<span class="badge-soft-success">Active</span>
|
|
<?php elseif ($partner['status'] === 'suspended'): ?>
|
|
<span class="badge-soft-warning">Suspended</span>
|
|
<?php else: ?>
|
|
<span class="badge-soft-muted">Inactive</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
|
|
<td class="text-end">
|
|
<button type="button"
|
|
class="btn-action btnContacts"
|
|
data-id="<?= e($partner['idpartner']); ?>"
|
|
data-name="<?= e($partner['partner_name']); ?>"
|
|
title="Contacts">
|
|
<i class="bx bx-user-pin"></i>
|
|
</button>
|
|
|
|
<button type="button"
|
|
class="btn-action btnEditPartner"
|
|
data-id="<?= e($partner['idpartner']); ?>"
|
|
title="Edit">
|
|
<i class="bx bx-edit-alt"></i>
|
|
</button>
|
|
|
|
<?php if ($partner['status'] === 'active'): ?>
|
|
<button type="button"
|
|
class="btn-action btnChangePartnerStatus"
|
|
data-id="<?= e($partner['idpartner']); ?>"
|
|
data-status="inactive"
|
|
title="Set inactive">
|
|
<i class="bx bx-toggle-right"></i>
|
|
</button>
|
|
<?php else: ?>
|
|
<button type="button"
|
|
class="btn-action btnChangePartnerStatus"
|
|
data-id="<?= e($partner['idpartner']); ?>"
|
|
data-status="active"
|
|
title="Set active">
|
|
<i class="bx bx-toggle-left"></i>
|
|
</button>
|
|
<?php endif; ?>
|
|
|
|
<button type="button"
|
|
class="btn-action danger btnDeletePartner"
|
|
data-id="<?= e($partner['idpartner']); ?>"
|
|
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>
|
|
|
|
<!-- Partner Modal -->
|
|
<div class="modal fade" id="partnerModal" tabindex="-1" aria-labelledby="partnerModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
|
<form id="partnerForm" class="modal-content">
|
|
<input type="hidden" name="action" value="save_partner">
|
|
<input type="hidden" name="idpartner" id="idpartner" value="0">
|
|
|
|
<div class="modal-header">
|
|
<div>
|
|
<h5 class="modal-title" id="partnerModalLabel">Add Business Partner</h5>
|
|
<small class="text-muted">Create or update a producer, supplier, vendor, factory or laboratory.</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">Partner Type <span class="required-dot">*</span></label>
|
|
<select class="form-select" name="partner_type" id="partner_type" required>
|
|
<?php foreach ($partnerTypes 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">Status</label>
|
|
<select class="form-select" name="status" id="status">
|
|
<?php foreach ($partnerStatuses as $value => $label): ?>
|
|
<option value="<?= e($value); ?>"><?= e($label); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Partner Name <span class="required-dot">*</span></label>
|
|
<input type="text" class="form-control" name="partner_name" id="partner_name" required>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label">Legal Name</label>
|
|
<input type="text" class="form-control" name="legal_name" id="legal_name">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">External Code</label>
|
|
<input type="text" class="form-control" name="external_code" id="external_code">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">VAT Number</label>
|
|
<input type="text" class="form-control" name="vat_number" id="vat_number">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Tax Code</label>
|
|
<input type="text" class="form-control" name="tax_code" id="tax_code">
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Address</label>
|
|
<input type="text" class="form-control" name="address" id="address">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">ZIP</label>
|
|
<input type="text" class="form-control" name="zip" id="zip">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">City</label>
|
|
<input type="text" class="form-control" name="city" id="city">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-5">
|
|
<label class="form-label">Country</label>
|
|
<select class="form-select" name="country_id" id="country_id">
|
|
<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 class="col-12 col-md-4">
|
|
<label class="form-label">Email</label>
|
|
<input type="email" class="form-control" name="email" id="email">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Phone</label>
|
|
<input type="text" class="form-control" name="phone" id="phone">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Website</label>
|
|
<input type="text" class="form-control" name="website" id="website">
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<label class="form-label">Notes</label>
|
|
<textarea class="form-control" name="notes" id="notes" rows="3"></textarea>
|
|
</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="btnSavePartner">
|
|
<i class="bx bx-save"></i>
|
|
Save Partner
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contacts Modal -->
|
|
<div class="modal fade" id="contactsModal" tabindex="-1" aria-labelledby="contactsModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<input type="hidden" id="contacts_idpartner" value="0">
|
|
|
|
<div class="modal-header">
|
|
<div>
|
|
<h5 class="modal-title" id="contactsModalLabel">Partner Contacts</h5>
|
|
<small class="text-muted" id="contactsPartnerName"></small>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
|
|
<div class="modal-body">
|
|
<div class="d-flex justify-content-end mb-3">
|
|
<button type="button" class="btn btn-primary btn-sm" id="btnAddContact">
|
|
<i class="bx bx-user-plus"></i>
|
|
Add Contact
|
|
</button>
|
|
</div>
|
|
|
|
<div id="contactsList" class="contact-list-wrapper">
|
|
<div class="empty-state">
|
|
<i class="bx bx-user-pin"></i>
|
|
<h6>Loading contacts...</h6>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="my-4">
|
|
|
|
<form id="contactForm">
|
|
<input type="hidden" name="action" value="save_contact">
|
|
<input type="hidden" name="idcontact" id="idcontact" value="0">
|
|
<input type="hidden" name="idpartner" id="contact_idpartner" value="0">
|
|
|
|
<h6 class="section-title mb-3" id="contactFormTitle">Add Contact</h6>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Contact Name <span class="required-dot">*</span></label>
|
|
<input type="text" class="form-control" name="contact_name" id="contact_name" required>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Role</label>
|
|
<input type="text" class="form-control" name="role" id="role">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" name="status" id="contact_status">
|
|
<?php foreach ($contactStatuses 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">Email</label>
|
|
<input type="email" class="form-control" name="email" id="contact_email">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Phone</label>
|
|
<input type="text" class="form-control" name="phone" id="contact_phone">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-4">
|
|
<label class="form-label">Mobile</label>
|
|
<input type="text" class="form-control" name="mobile" id="mobile">
|
|
</div>
|
|
|
|
<div class="col-12 col-md-3">
|
|
<label class="form-label">Primary Contact</label>
|
|
<select class="form-select" name="is_primary" id="is_primary">
|
|
<option value="0">No</option>
|
|
<option value="1">Yes</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-12 col-md-9">
|
|
<label class="form-label">Notes</label>
|
|
<input type="text" class="form-control" name="notes" id="contact_notes">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end gap-2 mt-3">
|
|
<button type="button" class="btn btn-light btn-sm" id="btnResetContactForm">
|
|
Reset
|
|
</button>
|
|
<button type="submit" class="btn btn-primary btn-sm" id="btnSaveContact">
|
|
<i class="bx bx-save"></i>
|
|
Save Contact
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include('jsinclude.php'); ?>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
if ($.fn.DataTable) {
|
|
$('#partnersTable').DataTable({
|
|
pageLength: 25,
|
|
order: [
|
|
[1, 'asc'],
|
|
[0, 'asc']
|
|
],
|
|
responsive: true,
|
|
language: {
|
|
search: "",
|
|
searchPlaceholder: "Search partners..."
|
|
}
|
|
});
|
|
}
|
|
|
|
const partnerModal = new bootstrap.Modal(document.getElementById('partnerModal'));
|
|
const contactsModal = new bootstrap.Modal(document.getElementById('contactsModal'));
|
|
|
|
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 resetPartnerForm() {
|
|
$('#partnerForm')[0].reset();
|
|
$('#idpartner').val('0');
|
|
$('#partnerModalLabel').text('Add Business Partner');
|
|
$('#partner_type').val('supplier');
|
|
$('#status').val('active');
|
|
}
|
|
|
|
function resetContactForm() {
|
|
$('#contactForm')[0].reset();
|
|
$('#idcontact').val('0');
|
|
$('#contact_idpartner').val($('#contacts_idpartner').val());
|
|
$('#contact_status').val('active');
|
|
$('#is_primary').val('0');
|
|
$('#contactFormTitle').text('Add Contact');
|
|
}
|
|
|
|
function renderContacts(contacts) {
|
|
if (!contacts || contacts.length === 0) {
|
|
$('#contactsList').html(`
|
|
<div class="empty-state">
|
|
<i class="bx bx-user-pin"></i>
|
|
<h6>No contacts yet</h6>
|
|
<p class="mb-0">Add the first contact for this business partner.</p>
|
|
</div>
|
|
`);
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
contacts.forEach(function(contact) {
|
|
const primaryBadge = parseInt(contact.is_primary) === 1 ?
|
|
'<span class="badge-soft-success">Primary</span>' :
|
|
'';
|
|
|
|
const statusBadge = contact.status === 'active' ?
|
|
'<span class="badge-soft-primary">Active</span>' :
|
|
'<span class="badge-soft-muted">Inactive</span>';
|
|
|
|
html += `
|
|
<div class="contact-row">
|
|
<div>
|
|
<div class="contact-name">${escapeHtml(contact.contact_name || '')} ${primaryBadge}</div>
|
|
<div class="contact-sub">
|
|
${escapeHtml(contact.role || '-')}
|
|
${contact.email ? ' | ' + escapeHtml(contact.email) : ''}
|
|
${contact.phone ? ' | ' + escapeHtml(contact.phone) : ''}
|
|
${contact.mobile ? ' | ' + escapeHtml(contact.mobile) : ''}
|
|
</div>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
${statusBadge}
|
|
<button type="button" class="btn-action btnEditContact" data-id="${contact.idcontact}" title="Edit contact">
|
|
<i class="bx bx-edit-alt"></i>
|
|
</button>
|
|
<button type="button" class="btn-action danger btnDeleteContact" data-id="${contact.idcontact}" title="Delete contact">
|
|
<i class="bx bx-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
$('#contactsList').html(html);
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
return String(value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function loadContacts(idpartner) {
|
|
$('#contactsList').html(`
|
|
<div class="empty-state">
|
|
<i class="bx bx-loader-alt bx-spin"></i>
|
|
<h6>Loading contacts...</h6>
|
|
</div>
|
|
`);
|
|
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'get_partner_contacts',
|
|
idpartner: idpartner
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to load contacts.');
|
|
return;
|
|
}
|
|
|
|
renderContacts(response.contacts);
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while loading contacts.');
|
|
}
|
|
});
|
|
}
|
|
|
|
$('#btnAddPartner, #btnAddPartnerSmall').on('click', function() {
|
|
resetPartnerForm();
|
|
partnerModal.show();
|
|
});
|
|
|
|
$('.btnEditPartner').on('click', function() {
|
|
const idpartner = $(this).data('id');
|
|
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'get_partner',
|
|
idpartner: idpartner
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to load partner.');
|
|
return;
|
|
}
|
|
|
|
const partner = response.partner;
|
|
|
|
$('#partnerModalLabel').text('Edit Business Partner');
|
|
$('#idpartner').val(partner.idpartner);
|
|
$('#idcompany').val(partner.idcompany);
|
|
$('#partner_type').val(partner.partner_type);
|
|
$('#partner_name').val(partner.partner_name);
|
|
$('#legal_name').val(partner.legal_name);
|
|
$('#external_code').val(partner.external_code);
|
|
$('#vat_number').val(partner.vat_number);
|
|
$('#tax_code').val(partner.tax_code);
|
|
$('#address').val(partner.address);
|
|
$('#city').val(partner.city);
|
|
$('#zip').val(partner.zip);
|
|
$('#country_id').val(partner.country_id);
|
|
$('#email').val(partner.email);
|
|
$('#phone').val(partner.phone);
|
|
$('#website').val(partner.website);
|
|
$('#notes').val(partner.notes);
|
|
$('#status').val(partner.status);
|
|
|
|
partnerModal.show();
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while loading partner.');
|
|
}
|
|
});
|
|
});
|
|
|
|
$('#partnerForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
$('#btnSavePartner').prop('disabled', true).html('<i class="bx bx-loader-alt bx-spin"></i> Saving...');
|
|
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: $(this).serialize(),
|
|
success: function(response) {
|
|
$('#btnSavePartner').prop('disabled', false).html('<i class="bx bx-save"></i> Save Partner');
|
|
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to save partner.');
|
|
return;
|
|
}
|
|
|
|
partnerModal.hide();
|
|
reloadAfterSuccess(response.message || 'Partner saved successfully.');
|
|
},
|
|
error: function() {
|
|
$('#btnSavePartner').prop('disabled', false).html('<i class="bx bx-save"></i> Save Partner');
|
|
showAlert('error', 'Server error while saving partner.');
|
|
}
|
|
});
|
|
});
|
|
|
|
$('.btnChangePartnerStatus').on('click', function() {
|
|
const idpartner = $(this).data('id');
|
|
const status = $(this).data('status');
|
|
|
|
const message = status === 'active' ?
|
|
'Do you want to activate this partner?' :
|
|
'Do you want to set this partner as inactive?';
|
|
|
|
const doChange = function() {
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'change_partner_status',
|
|
idpartner: idpartner,
|
|
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();
|
|
}
|
|
});
|
|
|
|
$('.btnDeletePartner').on('click', function() {
|
|
const idpartner = $(this).data('id');
|
|
|
|
const doDelete = function() {
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'delete_partner',
|
|
idpartner: idpartner
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to delete partner.');
|
|
return;
|
|
}
|
|
|
|
reloadAfterSuccess(response.message || 'Partner deleted successfully.');
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while deleting partner.');
|
|
}
|
|
});
|
|
};
|
|
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Delete partner?',
|
|
text: 'The partner will be deleted only if it is not linked to samples or BOM parts.',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#dc2626',
|
|
cancelButtonColor: '#64748b',
|
|
confirmButtonText: 'Yes, delete'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
doDelete();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (confirm('Delete partner?')) {
|
|
doDelete();
|
|
}
|
|
});
|
|
|
|
$('.btnContacts').on('click', function() {
|
|
const idpartner = $(this).data('id');
|
|
const partnerName = $(this).data('name');
|
|
|
|
$('#contacts_idpartner').val(idpartner);
|
|
$('#contact_idpartner').val(idpartner);
|
|
$('#contactsPartnerName').text(partnerName);
|
|
resetContactForm();
|
|
|
|
loadContacts(idpartner);
|
|
|
|
contactsModal.show();
|
|
});
|
|
|
|
$('#btnAddContact, #btnResetContactForm').on('click', function() {
|
|
resetContactForm();
|
|
});
|
|
|
|
$('#contactForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
$('#btnSaveContact').prop('disabled', true).html('<i class="bx bx-loader-alt bx-spin"></i> Saving...');
|
|
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: $(this).serialize(),
|
|
success: function(response) {
|
|
$('#btnSaveContact').prop('disabled', false).html('<i class="bx bx-save"></i> Save Contact');
|
|
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to save contact.');
|
|
return;
|
|
}
|
|
|
|
resetContactForm();
|
|
loadContacts($('#contacts_idpartner').val());
|
|
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Done',
|
|
text: response.message || 'Contact saved successfully.',
|
|
timer: 1200,
|
|
showConfirmButton: false
|
|
});
|
|
}
|
|
},
|
|
error: function() {
|
|
$('#btnSaveContact').prop('disabled', false).html('<i class="bx bx-save"></i> Save Contact');
|
|
showAlert('error', 'Server error while saving contact.');
|
|
}
|
|
});
|
|
});
|
|
|
|
$(document).on('click', '.btnEditContact', function() {
|
|
const idcontact = $(this).data('id');
|
|
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'get_contact',
|
|
idcontact: idcontact
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to load contact.');
|
|
return;
|
|
}
|
|
|
|
const contact = response.contact;
|
|
|
|
$('#contactFormTitle').text('Edit Contact');
|
|
$('#idcontact').val(contact.idcontact);
|
|
$('#contact_idpartner').val(contact.idpartner);
|
|
$('#contact_name').val(contact.contact_name);
|
|
$('#role').val(contact.role);
|
|
$('#contact_email').val(contact.email);
|
|
$('#contact_phone').val(contact.phone);
|
|
$('#mobile').val(contact.mobile);
|
|
$('#is_primary').val(contact.is_primary);
|
|
$('#contact_notes').val(contact.notes);
|
|
$('#contact_status').val(contact.status);
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while loading contact.');
|
|
}
|
|
});
|
|
});
|
|
|
|
$(document).on('click', '.btnDeleteContact', function() {
|
|
const idcontact = $(this).data('id');
|
|
|
|
const doDelete = function() {
|
|
$.ajax({
|
|
url: 'business-partners.php',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
data: {
|
|
action: 'delete_contact',
|
|
idcontact: idcontact
|
|
},
|
|
success: function(response) {
|
|
if (!response.success) {
|
|
showAlert('error', response.message || 'Unable to delete contact.');
|
|
return;
|
|
}
|
|
|
|
resetContactForm();
|
|
loadContacts($('#contacts_idpartner').val());
|
|
},
|
|
error: function() {
|
|
showAlert('error', 'Server error while deleting contact.');
|
|
}
|
|
});
|
|
};
|
|
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Delete contact?',
|
|
text: 'This contact will be removed from the business partner.',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#dc2626',
|
|
cancelButtonColor: '#64748b',
|
|
confirmButtonText: 'Yes, delete'
|
|
}).then(function(result) {
|
|
if (result.isConfirmed) {
|
|
doDelete();
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (confirm('Delete contact?')) {
|
|
doDelete();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|