« Back to History
employee_register.php
|
20260723_000646.php
Initial Domain Snapshot
Copy Code
<?php /* ============================================================================= File: /erp/employee_register.php Purpose: Employee Registration → company_employee_master (+ salary history) Masters: company_departments, company_designation, company_salary_types, company_payroll_periods Notes : Schema-aware master loaders + schema-aware users insert (Create Login) Scope : Page-local only. No global/base edits. ============================================================================= */ error_reporting(E_ALL); ini_set('display_errors', 1); require_once __DIR__ . '/modules/activity/activity_logger.php'; /* ---------- Auth + ACL + PDO (scoped) ---------- */ // Use page_acl if available for consistent bootstrap; fallback to auth.php if not. $acl_path = __DIR__ . '/modules/auth/page_acl.php'; if (is_file($acl_path)) { require_once $acl_path; $ctx = page_require_access('employee_register'); // may throw/redirect if not allowed $u = $ctx['user'] ?? null; $company_id = (int)($ctx['company_id'] ?? 0); $user_id = (int)($u['id'] ?? 0); $role_name = (string)($u['role'] ?? 'Employee'); $pdo = $ctx['pdo'] ?? null; } else { // fallback to older auth include if page_acl not available require_once __DIR__ . '/modules/auth/auth.php'; require_login(); $u = auth_user(); $company_id = (int)($u['company_id'] ?? 0); $user_id = (int)($u['id'] ?? 0); $role_name = (string)($u['role'] ?? 'Employee'); $pdo = $GLOBALS['pdo'] ?? null; } if (!$pdo || !($pdo instanceof PDO)) { require_once __DIR__ . '/core/db.php'; // core/db.php is expected to set $pdo if (!isset($pdo) || !($pdo instanceof PDO)) { throw new RuntimeException("Database connection not available (\$pdo missing)."); } } /* ---------- Ensure required tables (DDL safe) ---------- */ /* NOTE: these CREATE statements are idempotent and safe to run at page load. If you prefer to remove DDL from runtime, move these to migrations. */ $pdo->exec(" CREATE TABLE IF NOT EXISTS company_employee_master ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, employee_code VARCHAR(30) NULL, name VARCHAR(120) NOT NULL, mobile VARCHAR(20) NOT NULL, email VARCHAR(191) NULL, department_id BIGINT NOT NULL, department_name VARCHAR(120) NOT NULL, designation_id BIGINT NULL, designation_name VARCHAR(120) NULL, payroll_period_id BIGINT NULL, payroll_period_name VARCHAR(120) NULL, salary_type_id BIGINT NOT NULL, salary_type_name VARCHAR(60) NOT NULL, per_day DECIMAL(10,2) NULL, basic_salary DECIMAL(12,2) NULL, is_active TINYINT(1) NOT NULL DEFAULT 1, notes VARCHAR(255) NULL, created_by BIGINT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uniq_company_mobile (company_id, mobile) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $pdo->exec(" CREATE TABLE IF NOT EXISTS employee_salary ( id BIGINT PRIMARY KEY AUTO_INCREMENT, company_id BIGINT NOT NULL, employee_id BIGINT NOT NULL, salary_type_id BIGINT NULL, salary_type VARCHAR(60) NOT NULL, per_day DECIMAL(10,2) NULL, basic_salary DECIMAL(12,2) NULL, effective_from DATE NULL, effective_to DATE NULL, active TINYINT(1) NOT NULL DEFAULT 1, notes VARCHAR(255) NULL, created_by BIGINT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_company_emp (company_id, employee_id), INDEX idx_company_active (company_id, active) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); /* ---------- Helpers ---------- */ function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function col_exists(PDO $pdo, $table, $col){ $st = $pdo->prepare("SHOW COLUMNS FROM `$table` LIKE ?"); $st->execute([$col]); return (bool)$st->fetch(PDO::FETCH_ASSOC); } function any_present(PDO $pdo, $table, array $cands){ foreach ($cands as $c) { if (col_exists($pdo,$table,$c)) return $c; } return null; } /** Schema-aware master fetcher → rows: [id, name, code] */ function fetch_master(PDO $pdo, string $table, int $company_id, array $nameCands, array $codeCands){ $hasCompany = col_exists($pdo,$table,'company_id'); $activeCol = any_present($pdo,$table,['is_active','active','enabled']); $nameCol = any_present($pdo,$table,$nameCands); $codeCol = any_present($pdo,$table,$codeCands); if (!$nameCol) { $cols = $pdo->query("SHOW COLUMNS FROM `$table`")->fetchAll(PDO::FETCH_COLUMN); foreach ($cols as $c) { if ($c!=='id' && $c!=='company_id') { $nameCol=$c; break; } } } // try current company first $sql1 = "SELECT id, `$nameCol` AS name, ".($codeCol?"`$codeCol`":"NULL")." AS code FROM `$table`"; $w=[]; $p=[]; if ($hasCompany){ $w[]="company_id=?"; $p[]=$company_id; } if ($activeCol){ $w[]="`$activeCol`=1"; } if ($w) $sql1 .= " WHERE ".implode(" AND ",$w); $sql1 .= " ORDER BY ".($nameCol?"`$nameCol`":"id"); $st=$pdo->prepare($sql1); $st->execute($p); $rows=$st->fetchAll(PDO::FETCH_ASSOC); if ($rows) return $rows; // fallback: all active (any company), prioritize current company $sql2 = "SELECT id, `$nameCol` AS name, ".($codeCol?"`$codeCol`":"NULL")." AS code, ".($hasCompany?"company_id":"0")." AS cmp FROM `$table`"; $w=[]; $p=[]; if ($activeCol){ $w[]="`$activeCol`=1"; } if ($w) $sql2 .= " WHERE ".implode(" AND ",$w); $sql2 .= " ORDER BY CASE WHEN ".($hasCompany?"company_id":"0")."=? THEN 0 ELSE 1 END, ".($nameCol?"`$nameCol`":"id"); $st=$pdo->prepare($sql2); $st->execute([$company_id]); return $st->fetchAll(PDO::FETCH_ASSOC); } function normalize_user_role($role_text){ $r=strtolower(trim($role_text)); return in_array($r,['owner','admin','manager','developer'],true)?ucfirst($r):'Employee'; } function is_loom_karigar($dept_name,$role_name){ return (strcasecmp($dept_name,'loom')===0 && strcasecmp($role_name,'karigar')===0); } /* ---------- Load Masters (safe) ---------- */ $DEPTS = fetch_master($pdo, 'company_departments', $company_id, ['name','title','label'], ['code','short_code','slug']); $ROLES = fetch_master($pdo, 'company_designation', $company_id, ['name','title','label','designation','role'], ['code','short_code','slug']); $STYPES= fetch_master($pdo, 'company_salary_types', $company_id, ['name','title','label'], ['code','short_code','slug']); $PPERS = fetch_master($pdo, 'company_payroll_periods', $company_id, ['payroll_type','name','title','label','period_name','period_label'], ['code','short_code','slug']); /* ---------- Handle POST ---------- */ $flash=''; $err=''; $new_emp_id=null; if($_SERVER['REQUEST_METHOD']==='POST'){ $emp_code = trim($_POST['employee_code']??''); $name = trim($_POST['name']??''); $deptId = (int)($_POST['department_id']??0); $roleId = (int)($_POST['designation_id']??0); $mobile = preg_replace('/\s+/', '', (string)($_POST['mobile']??'')); $email = trim($_POST['email']??''); $stypeId = (int)($_POST['salary_type_id']??0); $ppId = (int)($_POST['payroll_period_id']??0); $perDay = isset($_POST['per_day']) ? (float)$_POST['per_day'] : null; $basic = isset($_POST['basic_salary']) ? (float)$_POST['basic_salary'] : null; $notes = trim($_POST['notes']??''); $mk_user = isset($_POST['make_login']) ? 1 : 0; try{ if($name===''||$deptId<=0||$stypeId<=0||$mobile==='') throw new Exception("Name, Department, Salary Type, Mobile आवश्यक हैं।"); // Map IDs → snapshots $dept = array_values(array_filter($DEPTS, fn($d)=>(int)$d['id']===$deptId))[0] ?? null; $role = $roleId>0 ? (array_values(array_filter($ROLES, fn($r)=>(int)$r['id']===$roleId))[0] ?? null) : null; $stype= array_values(array_filter($STYPES, fn($s)=>(int)$s['id']===$stypeId))[0] ?? null; $pper = $ppId>0 ? (array_values(array_filter($PPERS, fn($p)=>(int)$p['id']===$ppId))[0] ?? null) : null; if(!$dept) throw new Exception("Invalid Department."); if(!$stype) throw new Exception("Invalid Salary Type."); $stypeName = strtolower(trim($stype['name']??'')); if($stypeName==='attendance' && !($perDay>0)) throw new Exception("Attendance salary के लिए Per Day आवश्यक है।"); if(($stypeName==='fixed' || $stypeName==='fixed attendance') && !($basic>0)) throw new Exception("Fixed/Fixed Attendance के लिए Basic Salary आवश्यक है।"); // Duplicate mobile guard $chk=$pdo->prepare("SELECT id FROM company_employee_master WHERE company_id=? AND mobile=? LIMIT 1"); $chk->execute([$company_id,$mobile]); if($chk->fetch()) throw new Exception("इस mobile पर employee पहले से मौजूद है।"); $pdo->beginTransaction(); // Insert master record $ins=$pdo->prepare("INSERT INTO company_employee_master (company_id,employee_code,name,mobile,email, department_id,department_name,designation_id,designation_name, payroll_period_id,payroll_period_name, salary_type_id,salary_type_name,per_day,basic_salary, is_active,notes,created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); $ins->execute([ $company_id, ($emp_code!==''?$emp_code:null), $name, $mobile, ($email!==''?$email:null), $dept['id'], $dept['name'], ($role? $role['id']:null), ($role? $role['name']:null), ($pper? $pper['id']:null), ($pper? $pper['name']:null), $stype['id'], $stype['name'], ($stypeName==='attendance'? $perDay : null), (($stypeName==='fixed'||$stypeName==='fixed attendance')? $basic : null), 1, ($notes!==''?$notes:null), $user_id ]); $new_emp_id = (int)$pdo->lastInsertId(); // Salary history (deactivate old → insert new active) $pdo->prepare("UPDATE employee_salary SET active=0, effective_to=CURDATE() WHERE company_id=? AND employee_id=? AND active=1") ->execute([$company_id,$new_emp_id]); $insS=$pdo->prepare("INSERT INTO employee_salary (company_id,employee_id,salary_type_id,salary_type,per_day,basic_salary,effective_from,active,notes,created_by) VALUES (?,?,?,?,?,?,?,?,?,?)"); $insS->execute([ $company_id,$new_emp_id,$stype['id'],$stype['name'], ($stypeName==='attendance'? $perDay : null), (($stypeName==='fixed'||$stypeName==='fixed attendance')? $basic : null), date('Y-m-d'),1, ($notes!==''?$notes:null), $user_id ]); // Optional Login (schema-aware) if ($mk_user) { try { $has_username = col_exists($pdo,'users','username'); $has_email = col_exists($pdo,'users','email'); $has_company = col_exists($pdo,'users','company_id'); $has_role = col_exists($pdo,'users','role'); $has_name = col_exists($pdo,'users','name'); $dup = false; $or = []; $params = []; if ($has_username){ $or[]='username=?'; $params[]=$mobile; } if ($has_email){ $or[]='email=?'; $params[]=$mobile; } if ($or){ $sqlDup = "SELECT id FROM users WHERE ".($has_company ? "company_id=? AND " : "")."(".implode(' OR ', $or).") LIMIT 1"; $dupStmt = $pdo->prepare($sqlDup); $dupStmt->execute(array_merge($has_company?[$company_id]:[], $params)); $dup = (bool)$dupStmt->fetch(); } if (!$dup) { $cols = []; $ph = []; $vals = []; if ($has_company){ $cols[]='company_id'; $ph[]='?'; $vals[]=$company_id; } if ($has_name){ $cols[]='name'; $ph[]='?'; $vals[]=$name; } if ($has_username){$cols[]='username'; $ph[]='?'; $vals[]=$mobile; } if ($has_email){ $cols[]='email'; $ph[]='?'; $vals[]=$mobile; } $cols[]='password_hash'; $ph[]='?'; $vals[] = password_hash('12345678', PASSWORD_DEFAULT); if ($has_role){ $cols[]='role'; $ph[]='?'; $vals[] = normalize_user_role($role['name'] ?? ''); } $sqlIns = "INSERT INTO users (".implode(',', $cols).") VALUES (".implode(',', $ph).")"; $pdo->prepare($sqlIns)->execute($vals); } } catch (Throwable $ex) { $flash .= " (Login skipped: ".$ex->getMessage().")"; } } $pdo->commit(); activity_log([ 'company_id' => $company_id ?? 0, 'user_id' => $u['id'] ?? ($_SESSION['user_id'] ?? 0), 'module' => 'employee', 'action_name' => 'create', 'entity_type' => 'employee_register', 'entity_id' => $new_emp_id ?? 0, 'remarks' => 'Employee created' ]); if(is_loom_karigar($dept['name'], $role['name'] ?? '')){ header("Location: /erp/loom_employee_register.php?employee_id=".$new_emp_id); exit; } $flash = "Employee saved (ID: {$new_emp_id})."; }catch(Throwable $e){ if($pdo->inTransaction()) $pdo->rollBack(); $err = $e->getMessage(); } } /* ---------- UI ---------- */ $PAGE_TITLE = 'Employee Register'; $PAGE_ID = 'employee_register'; ?><!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="/erp/css/employee_register.css?v=20251121"> <meta charset="utf-8"> <title><?= h($PAGE_TITLE) ?></title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- page styles moved to main.css (page: employee_register) --> <style> /* =============================== GLOBAL PAGE BACKGROUND =============================== */ body { background: #f3f4f6 !important; /* ERP light gray */ font-family: 'Inter', 'Roboto', Arial, sans-serif !important; margin: 0 !important; } /* =============================== WRAP / CENTERING =============================== */ .wrap, .wrap-centered { display: flex !important; justify-content: center !important; align-items: flex-start !important; min-height: 90vh !important; } /* =============================== CARD (ERP WHITE) =============================== */ .card.page-card, .page-card, .card { background: #ffffff !important; /* WHITE card */ border-radius: 14px !important; box-shadow: 0 6px 20px rgba(0,0,0,0.08) !important; color: #111827 !important; padding: 36px 34px 30px 34px !important; margin: 32px auto 0 auto !important; max-width: 650px !important; width: 100%; display: block !important; border: 1px solid #e5e7eb !important; } /* =============================== CARD HEADER =============================== */ .page-card__header { border-bottom: 1px solid #e5e7eb !important; padding-bottom: 16px !important; margin-bottom: 22px !important; } .page-card__header h1 { font-size: 1.75rem !important; font-weight: 600 !important; margin: 0 0 6px !important; color: #111827 !important; } .muted { color: #6b7280 !important; font-size: 14px !important; } /* =============================== ACTION BUTTONS (TOP) =============================== */ .action-group .btn { background: #16a34a !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; padding: 8px 16px !important; font-size: 0.95rem !important; font-weight: 600 !important; margin-left: 10px !important; transition: background 0.18s !important; text-decoration: none !important; } .action-group .btn:hover { background: #15803d !important; color: #ffffff !important; } /* =============================== FORM GRID =============================== */ .erp-form-card { margin: 0 !important; padding: 0 !important; } .erp-form-grid { display: grid !important; grid-template-columns: 1fr 1fr !important; gap: 20px 26px !important; } /* =============================== FORM FIELDS =============================== */ .field { font-size: 0.95rem !important; color: #374151 !important; display: flex; flex-direction: column; } .field label { color: #374151 !important; margin-bottom: 6px !important; font-weight: 500 !important; } /* =============================== INPUTS / SELECTS =============================== */ input[type="text"], input[type="number"], input[type="date"], input[type="email"], select, textarea, .input-text, .input-select { width: 100% !important; padding: 10px 14px !important; font-size: 0.95rem !important; border: 1px solid #d1d5db !important; background: #ffffff !important; color: #111827 !important; border-radius: 8px !important; box-sizing: border-box !important; outline: none !important; transition: border 0.18s, box-shadow 0.18s !important; } input:focus, select:focus, textarea:focus { border-color: #16a34a !important; box-shadow: 0 0 0 2px rgba(22,163,74,0.15) !important; } textarea { min-height: 44px !important; } /* =============================== CHECKBOX =============================== */ input[type="checkbox"] { accent-color: #16a34a !important; margin-right: 6px !important; } /* =============================== PRIMARY BUTTON =============================== */ .btn, .btn--primary { background: #16a34a !important; color: #ffffff !important; border: none !important; border-radius: 8px !important; padding: 9px 22px !important; font-size: 0.95rem !important; font-weight: 600 !important; cursor: pointer !important; transition: background 0.18s !important; text-decoration: none !important; } .btn:hover, .btn--primary:hover { background: #15803d !important; color: #ffffff !important; } /* =============================== FORM ACTIONS =============================== */ .form-actions { grid-column: 1 / -1 !important; text-align: center !important; margin-top: 22px !important; } /* =============================== NOTICES =============================== */ .notice { max-width: 600px !important; margin: 20px auto !important; border-radius: 8px !important; font-size: 0.95rem !important; padding: 12px 20px !important; background: #ecfdf5 !important; color: #065f46 !important; border: 1px solid #bbf7d0 !important; } .notice--error { background: #fef2f2 !important; color: #991b1b !important; border: 1px solid #fecaca !important; } /* =============================== SMALL PILLS =============================== */ .pill, .pill.ok { display: inline-block !important; background: #16a34a !important; color: #ffffff !important; border-radius: 14px !important; font-size: 0.85rem !important; padding: 4px 14px !important; text-decoration: none !important; } /* =============================== HELPERS =============================== */ .hide { display: none !important; } /* =============================== MOBILE =============================== */ @media (max-width: 850px) { .card.page-card, .card { padding: 18px 14px !important; max-width: 96vw !important; } .erp-form-grid { grid-template-columns: 1fr !important; gap: 14px !important; } .form-actions { margin-top: 16px !important; } } </style> </head> <body> <?php // robust header include (search multiple likely paths) $header_paths = [ __DIR__ . '/partials/header.php', __DIR__ . '/../partials/header.php', __DIR__ . '/erp/partials/header.php', __DIR__ . '/../erp/partials/header.php', ]; foreach ($header_paths as $hp) { if (is_file($hp)) { include_once $hp; break; } } ?> <div class="wrap wrap-centered"> <?php if(!empty($flash)): ?><div class="card notice notice--success"><?= h($flash) ?></div><?php endif; ?> <?php if(!empty($err)): ?><div class="card notice notice--error"><?= h($err) ?></div><?php endif; ?> <div class="card page-card"> <div class="page-card__header" style="display:flex;align-items:flex-end;justify-content:space-between;gap:12px;flex-wrap:wrap"> <div> <h1 style="margin:0 0 6px"><?= h($PAGE_TITLE) ?></h1> <div class="muted">Logged in as: <b><?= h($u['name'] ?? 'User') ?></b> (<?= h($role_name) ?>)</div> </div> <div class="action-group"> <a class="btn btn--muted" href="/erp/employee_list.php">Employee List</a> <a class="btn btn--muted" href="/erp/company_departments.php">Departments</a> <a class="btn btn--muted" href="/erp/company_designation.php">Designations</a> </div> </div> <form method="post" id="regForm" autocomplete="off" class="erp-form-card" style="margin-top:12px"> <div class="erp-form-grid"> <label class="field">Employee Code <input name="employee_code" placeholder="Optional" class="input-text"> </label> <label class="field">Name <input name="name" required class="input-text"> </label> <div class="field"> <label>Department <select name="department_id" id="department_id" required class="input-select"> <option value="">Select Department</option> <?php foreach($DEPTS as $d): ?> <option value="<?= (int)$d['id'] ?>" data-name="<?= h($d['name']) ?>"> <?= h($d['name']) ?><?= $d['code']? " (".h($d['code']).")":"" ?> </option> <?php endforeach; ?> </select> </label> <div style="margin-top:6px"> <a class="pill pill.ok" href="/erp/company_departments.php">+ Add Department</a> </div> </div> <div class="field"> <label>Designation (Role) <select name="designation_id" id="designation_id" class="input-select"> <option value="">Select Designation</option> <?php foreach($ROLES as $r): ?> <option value="<?= (int)$r['id'] ?>" data-name="<?= h($r['name']) ?>"> <?= h($r['name']) ?><?= $r['code']? " (".h($r['code']).")":"" ?> </option> <?php endforeach; ?> </select> </label> <div style="margin-top:6px"> <a class="pill pill.ok" href="/erp/company_designation.php">+ Add Designation</a> </div> </div> <label class="field">Payroll Period <select name="payroll_period_id" id="payroll_period_id" class="input-select"> <option value="">Select Payroll Period</option> <?php foreach($PPERS as $p): ?> <option value="<?= (int)$p['id'] ?>" data-name="<?= h($p['name']) ?>"> <?= h($p['name']) ?><?= $p['code']? " (".h($p['code']).")":"" ?> </option> <?php endforeach; ?> </select> </label> <label class="field">Mobile <input name="mobile" required pattern="[0-9+\-\s]{6,}" class="input-text"> </label> <label class="field">Email <input name="email" type="email" placeholder="Optional" class="input-text"> </label> <label class="field">Salary Type <select name="salary_type_id" id="salary_type_id" required class="input-select"> <option value="">Select Salary Type</option> <?php foreach($STYPES as $s): ?> <option value="<?= (int)$s['id'] ?>" data-name="<?= h(strtolower($s['name'])) ?>"> <?= h($s['name']) ?><?= $s['code']? " (".h($s['code']).")":"" ?> </option> <?php endforeach; ?> </select> </label> <label id="perDayWrap" class="field hide">Per Day (₹) <input name="per_day" type="number" step="0.01" placeholder="e.g., 600" class="input-text"> </label> <label id="basicWrap" class="field hide">Basic Salary (₹) <input name="basic_salary" type="number" step="0.01" placeholder="e.g., 15000" class="input-text"> </label> <label class="field" style="grid-column:1/-1">Notes <textarea name="notes" placeholder="Optional" class="input-text"></textarea> </label> </div> <div class="mini-row" style="margin-top:10px"> <label style="display:flex;gap:8px;align-items:center"> <input type="checkbox" name="make_login" id="make_login"> Create Login (ID = Mobile, Password = 12345678) </label> </div> <div class="form-actions" style="margin-top:12px"> <a class="btn btn--muted" href="/erp/employee_list.php">Employee List</a> <button class="btn btn--primary" type="submit">Register</button> </div> </form> </div> </div> <?php // robust footer include $footer_paths = [ __DIR__ . '/partials/footer.php', __DIR__ . '/../partials/footer.php', __DIR__ . '/erp/partials/footer.php', __DIR__ . '/../erp/partials/footer.php', ]; foreach ($footer_paths as $fp) { if (is_file($fp)) { include_once $fp; break; } } ?> <script> const stSel = document.getElementById('salary_type_id'); const perDay = document.getElementById('perDayWrap'); const basic = document.getElementById('basicWrap'); function toggleSalaryFields(){ const opt = stSel && stSel.options[stSel.selectedIndex]; const name = (opt?.dataset?.name || '').toLowerCase(); perDay?.classList?.add('hide'); basic?.classList?.add('hide'); if (name === 'attendance') { perDay?.classList?.remove('hide'); } else if (name === 'fixed' || name === 'fixed attendance') { basic?.classList?.remove('hide'); } } if (stSel) { stSel.addEventListener('change', toggleSalaryFields); toggleSalaryFields(); } </script> </body> </html>