# KAYAL STORE FINANCE - REPOSITORY
================================================================================
Project Name: Kayal Store Finance
Created: 2025-09-29 03:27:27
Last Updated: 2025-09-29 03:28:01
Source ZIP: public_html.zip
Total Files: 19
Total Folders: 5
================================================================================
## FILE STRUCTURE
================================================================================
Kayal Store Finance/
├── assets
├── auth.php
├── bill.php
├── config/
│ └── database.php
├── css/
│ └── style.css
├── default.php
├── expenses.php
├── finance.php
├── includes/
│ ├── footer.php
│ └── header.php
├── index.php
├── js/
│ └── script.js
├── loan_calculator.php
├── login.php
├── logout.php
├── payment_slip.php
├── sales.php
├── settings.php
├── setup.php
└── users.php
================================================================================
## FILE CONTENTS
================================================================================
### FILE 1: auth.php
- Type: PHP
- Size: 3.83 KB
- Path: .
- Name: auth.php
------------------------------------------------------------
pdo = $pdo;
}
public function login($username, $password) {
try {
$stmt = $this->pdo->prepare("SELECT id, full_name, phone, username, password, role FROM users WHERE username = ? AND status = 'active'");
$stmt->execute([$username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['full_name'] = $user['full_name'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
$_SESSION['phone'] = $user['phone'];
// Create session token for additional security
$token = bin2hex(random_bytes(32));
$_SESSION['token'] = $token;
// Store session in database
$expires = date('Y-m-d H:i:s', strtotime('+24 hours'));
$stmt = $this->pdo->prepare("INSERT INTO user_sessions (user_id, session_token, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$user['id'], $token, $expires]);
return true;
}
return false;
} catch (PDOException $e) {
return false;
}
}
public function logout() {
if (isset($_SESSION['user_id']) && isset($_SESSION['token'])) {
// Remove session from database
$stmt = $this->pdo->prepare("DELETE FROM user_sessions WHERE user_id = ? AND session_token = ?");
$stmt->execute([$_SESSION['user_id'], $_SESSION['token']]);
}
session_destroy();
header("Location: login.php");
exit();
}
public function isLoggedIn() {
return isset($_SESSION['user_id']) && isset($_SESSION['username']);
}
public function requireLogin() {
if (!$this->isLoggedIn()) {
header("Location: login.php");
exit();
}
}
public function requireAdmin() {
$this->requireLogin();
if ($_SESSION['role'] !== 'admin') {
header("Location: index.php");
exit();
}
}
public function isSetupCompleted() {
try {
$stmt = $this->pdo->prepare("SELECT setting_value FROM app_settings WHERE setting_key = 'setup_completed'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result && $result['setting_value'] == '1';
} catch (PDOException $e) {
return false;
}
}
public function createFirstAdmin($full_name, $phone, $username, $password) {
try {
// Check if setup is already completed
if ($this->isSetupCompleted()) {
return false;
}
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = $this->pdo->prepare("INSERT INTO users (full_name, phone, username, password, role) VALUES (?, ?, ?, ?, 'admin')");
$result = $stmt->execute([$full_name, $phone, $username, $hashed_password]);
if ($result) {
// Mark setup as completed
$stmt = $this->pdo->prepare("UPDATE app_settings SET setting_value = '1' WHERE setting_key = 'setup_completed'");
$stmt->execute();
return true;
}
return false;
} catch (PDOException $e) {
return false;
}
}
public function getUserRole() {
return $_SESSION['role'] ?? null;
}
public function getUserName() {
return $_SESSION['full_name'] ?? '';
}
}
$auth = new Auth($pdo);
?>
-------------------- END OF FILE --------------------
### FILE 2: bill.php
- Type: PHP
- Size: 15.91 KB
- Path: .
- Name: bill.php
------------------------------------------------------------
requireLogin();
// Get sale ID from URL
$sale_id = $_GET['sale_id'] ?? 0;
if (!$sale_id) {
header("Location: sales.php");
exit();
}
try {
// Fetch sale details
$stmt = $pdo->prepare("
SELECT s.*, u.full_name as created_by_name
FROM sales s
JOIN users u ON s.created_by = u.id
WHERE s.id = ?
");
$stmt->execute([$sale_id]);
$sale = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$sale) {
header("Location: sales.php");
exit();
}
// Fetch sale items
$stmt = $pdo->prepare("
SELECT si.*, ps.name as product_name, u.unit_symbol
FROM sales_items si
JOIN products_services ps ON si.product_service_id = ps.id
JOIN units u ON ps.unit_id = u.id
WHERE si.sale_id = ?
ORDER BY si.id
");
$stmt->execute([$sale_id]);
$sale_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
header("Location: sales.php");
exit();
}
// Calculate totals
$subtotal = $sale['total_amount'];
$gst_amount = 0; // GST is 0% as requested
$total_amount = $subtotal + $gst_amount;
?>
Bill -
SALES INVOICE
Bill Details
Bill Number:
Date:
Time:
Served By:
Customer Details
Name:
Phone:
Payment Status:
Payment Method:
| S.No |
Product/Service |
Quantity |
Unit |
Rate (₹) |
Amount (₹) |
|
|
|
|
|
|
| Subtotal: |
₹ |
| GST (0%): |
₹ |
| TOTAL AMOUNT: |
₹ |
Payment Status:
✅ PAID via
⏳ PAYMENT PENDING
⚠️ PARTIAL PAYMENT RECEIVED
Notes:
-------------------- END OF FILE --------------------
### FILE 3: default.php
- Type: PHP
- Size: 15.99 KB
- Path: .
- Name: default.php
------------------------------------------------------------
Default page
You Are All Set to Go!
All you have to do now is upload your website files and start your journey. Check out how to do that below:
-------------------- END OF FILE --------------------
### FILE 4: expenses.php
- Type: PHP
- Size: 34.05 KB
- Path: .
- Name: expenses.php
------------------------------------------------------------
requireLogin();
$page_title = 'Expenses Management';
$success_message = '';
$error_message = '';
// Get edit data if editing
$edit_expense = null;
if (isset($_GET['edit_expense'])) {
try {
$edit_stmt = $pdo->prepare("
SELECT e.*, ec.category_name, es.subcategory_name, u.full_name as paid_by_name
FROM expenses e
JOIN expense_categories ec ON e.category_id = ec.id
LEFT JOIN expense_subcategories es ON e.subcategory_id = es.id
JOIN users u ON e.paid_by = u.id
WHERE e.id = ?
");
$edit_stmt->execute([$_GET['edit_expense']]);
$edit_expense = $edit_stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error_message = 'Error fetching expense for editing.';
}
}
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
if ($_POST['action'] == 'add_expense' || $_POST['action'] == 'edit_expense') {
$expense_id = $_POST['expense_id'] ?? null;
$category_id = $_POST['category_id'] ?? 0;
$subcategory_id = $_POST['subcategory_id'] ?? null;
$description = trim($_POST['description'] ?? '');
$amount = $_POST['amount'] ?? 0;
$expense_date = $_POST['expense_date'] ?? '';
$paid_to = trim($_POST['paid_to'] ?? '');
$paid_by = $_POST['paid_by'] ?? 0;
$paid_through = $_POST['paid_through'] ?? '';
$notes = trim($_POST['notes'] ?? '');
// Validation
if (empty($category_id) || empty($description) || empty($amount) || empty($expense_date) || empty($paid_to) || empty($paid_by) || empty($paid_through)) {
$error_message = 'All fields except subcategory and notes are required.';
} elseif (!is_numeric($amount) || $amount <= 0) {
$error_message = 'Please enter a valid amount greater than zero.';
} else {
try {
if ($_POST['action'] == 'edit_expense' && $expense_id) {
// Update existing expense
$stmt = $pdo->prepare("
UPDATE expenses SET category_id = ?, subcategory_id = ?, description = ?,
amount = ?, expense_date = ?, paid_to = ?, paid_by = ?,
paid_through = ?, notes = ?
WHERE id = ?
");
$stmt->execute([
$category_id, $subcategory_id ?: null, $description, $amount,
$expense_date, $paid_to, $paid_by, $paid_through, $notes, $expense_id
]);
$_SESSION['success_message'] = 'Expense updated successfully!';
} else {
// Create new expense
$expense_number = 'EXP' . date('YmdHis') . rand(100, 999);
$stmt = $pdo->prepare("
INSERT INTO expenses (expense_number, category_id, subcategory_id, description, amount,
expense_date, paid_to, paid_by, paid_through, notes, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$expense_number, $category_id, $subcategory_id ?: null, $description, $amount,
$expense_date, $paid_to, $paid_by, $paid_through, $notes, $_SESSION['user_id']
]);
$_SESSION['success_message'] = 'Expense added successfully! Reference: ' . $expense_number;
}
header("Location: expenses.php");
exit();
} catch (Exception $e) {
$error_message = 'Error processing expense: ' . $e->getMessage();
}
}
}
elseif ($_POST['action'] == 'delete_expense') {
$expense_id = intval($_POST['expense_id'] ?? 0);
if ($expense_id > 0) {
try {
$stmt = $pdo->prepare("DELETE FROM expenses WHERE id = ?");
$result = $stmt->execute([$expense_id]);
if ($result) {
$_SESSION['success_message'] = 'Expense deleted successfully.';
} else {
$_SESSION['error_message'] = 'Failed to delete expense.';
}
} catch (Exception $e) {
$_SESSION['error_message'] = 'Error deleting expense: ' . $e->getMessage();
}
}
header("Location: expenses.php");
exit();
}
elseif ($_POST['action'] == 'export_expenses') {
$export_filter_category = $_POST['export_filter_category'] ?? '';
$export_filter_subcategory = $_POST['export_filter_subcategory'] ?? '';
$export_filter_date_from = $_POST['export_filter_date_from'] ?? '';
$export_filter_date_to = $_POST['export_filter_date_to'] ?? '';
$export_filter_paid_by = $_POST['export_filter_paid_by'] ?? '';
$export_filter_paid_through = $_POST['export_filter_paid_through'] ?? '';
$export_filter_search = $_POST['export_filter_search'] ?? '';
// Build export query with same filters
$export_where_clause = "1=1";
$export_params = [];
if (!empty($export_filter_category)) {
$export_where_clause .= " AND e.category_id = ?";
$export_params[] = $export_filter_category;
}
if (!empty($export_filter_subcategory)) {
$export_where_clause .= " AND e.subcategory_id = ?";
$export_params[] = $export_filter_subcategory;
}
if (!empty($export_filter_date_from)) {
$export_where_clause .= " AND e.expense_date >= ?";
$export_params[] = $export_filter_date_from;
}
if (!empty($export_filter_date_to)) {
$export_where_clause .= " AND e.expense_date <= ?";
$export_params[] = $export_filter_date_to;
}
if (!empty($export_filter_paid_by)) {
$export_where_clause .= " AND e.paid_by = ?";
$export_params[] = $export_filter_paid_by;
}
if (!empty($export_filter_paid_through)) {
$export_where_clause .= " AND e.paid_through = ?";
$export_params[] = $export_filter_paid_through;
}
if (!empty($export_filter_search)) {
$export_where_clause .= " AND (e.description LIKE ? OR e.paid_to LIKE ? OR e.notes LIKE ?)";
$search_term = '%' . $export_filter_search . '%';
$export_params[] = $search_term;
$export_params[] = $search_term;
$export_params[] = $search_term;
}
try {
$export_stmt = $pdo->prepare("
SELECT e.expense_number, e.expense_date, ec.category_name,
COALESCE(es.subcategory_name, '-') as subcategory_name,
e.description, e.amount, e.paid_to, u.full_name as paid_by_name,
e.paid_through, e.notes
FROM expenses e
JOIN expense_categories ec ON e.category_id = ec.id
LEFT JOIN expense_subcategories es ON e.subcategory_id = es.id
JOIN users u ON e.paid_by = u.id
WHERE $export_where_clause
ORDER BY e.created_at DESC
");
$export_stmt->execute($export_params);
$export_data = $export_stmt->fetchAll(PDO::FETCH_ASSOC);
$filename = 'expenses_export_' . date('Y-m-d_H-i-s') . '.csv';
// Generate CSV
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$output = fopen('php://output', 'w');
// Add BOM for proper Excel UTF-8 support
fwrite($output, "\xEF\xBB\xBF");
// Headers
fputcsv($output, [
'Expense Number', 'Date', 'Category', 'Subcategory', 'Description',
'Amount', 'Paid To', 'Paid By', 'Payment Method', 'Notes'
]);
foreach ($export_data as $row) {
fputcsv($output, [
$row['expense_number'],
$row['expense_date'],
$row['category_name'],
$row['subcategory_name'],
$row['description'],
$row['amount'],
$row['paid_to'],
$row['paid_by_name'],
$row['paid_through'],
$row['notes']
]);
}
fclose($output);
exit();
} catch (PDOException $e) {
$error_message = 'Error exporting data: ' . $e->getMessage();
}
}
}
// Fetch categories for dropdowns
try {
$categories_stmt = $pdo->prepare("SELECT * FROM expense_categories WHERE status = 'active' ORDER BY category_name");
$categories_stmt->execute();
$categories = $categories_stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch subcategories for dropdowns
$subcategories_stmt = $pdo->prepare("
SELECT es.*, ec.category_name
FROM expense_subcategories es
JOIN expense_categories ec ON es.category_id = ec.id
WHERE es.status = 'active'
ORDER BY ec.category_name, es.subcategory_name
");
$subcategories_stmt->execute();
$subcategories = $subcategories_stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch users for dropdown
$users_stmt = $pdo->prepare("SELECT id, full_name FROM users WHERE status = 'active' ORDER BY full_name");
$users_stmt->execute();
$users = $users_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$categories = $subcategories = $users = [];
}
// Fetch expenses with filters
$filter_category = $_GET['filter_category'] ?? '';
$filter_subcategory = $_GET['filter_subcategory'] ?? '';
$filter_date_from = $_GET['filter_date_from'] ?? '';
$filter_date_to = $_GET['filter_date_to'] ?? '';
$filter_paid_by = $_GET['filter_paid_by'] ?? '';
$filter_paid_through = $_GET['filter_paid_through'] ?? '';
$filter_search = $_GET['filter_search'] ?? '';
$where_clause = "1=1";
$params = [];
if (!empty($filter_category)) {
$where_clause .= " AND e.category_id = ?";
$params[] = $filter_category;
}
if (!empty($filter_subcategory)) {
$where_clause .= " AND e.subcategory_id = ?";
$params[] = $filter_subcategory;
}
if (!empty($filter_date_from)) {
$where_clause .= " AND e.expense_date >= ?";
$params[] = $filter_date_from;
}
if (!empty($filter_date_to)) {
$where_clause .= " AND e.expense_date <= ?";
$params[] = $filter_date_to;
}
if (!empty($filter_paid_by)) {
$where_clause .= " AND e.paid_by = ?";
$params[] = $filter_paid_by;
}
if (!empty($filter_paid_through)) {
$where_clause .= " AND e.paid_through = ?";
$params[] = $filter_paid_through;
}
if (!empty($filter_search)) {
$where_clause .= " AND (e.description LIKE ? OR e.paid_to LIKE ? OR e.notes LIKE ?)";
$search_term = '%' . $filter_search . '%';
$params[] = $search_term;
$params[] = $search_term;
$params[] = $search_term;
}
try {
// Get expense stats
$stats_stmt = $pdo->prepare("
SELECT
COUNT(*) as total_expenses,
COALESCE(SUM(amount), 0) as total_amount
FROM expenses e WHERE $where_clause
");
$stats_stmt->execute($params);
$stats = $stats_stmt->fetch(PDO::FETCH_ASSOC);
// Get expense records
$expenses_stmt = $pdo->prepare("
SELECT e.*, ec.category_name,
COALESCE(es.subcategory_name, '-') as subcategory_name,
u.full_name as paid_by_name, uc.full_name as created_by_name
FROM expenses e
JOIN expense_categories ec ON e.category_id = ec.id
LEFT JOIN expense_subcategories es ON e.subcategory_id = es.id
JOIN users u ON e.paid_by = u.id
JOIN users uc ON e.created_by = uc.id
WHERE $where_clause
ORDER BY e.created_at DESC
LIMIT 100
");
$expenses_stmt->execute($params);
$expenses = $expenses_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$stats = ['total_expenses' => 0, 'total_amount' => 0];
$expenses = [];
$error_message = 'Error fetching expense data.';
}
include 'includes/header.php';
?>
No Expenses Found
Add your first expense using the form above.
| Expense# |
Date |
Category |
Description |
Amount |
Paid To |
Paid By |
Method |
Actions |
|
|
|
|
₹ |
|
|
'Cash',
'upi' => 'UPI',
'credit_card' => 'Credit Card'
];
echo $method_display[$expense['paid_through']] ?? ucfirst($expense['paid_through']);
?>
|
|
|
Notes:
|
-------------------- END OF FILE --------------------
### FILE 5: finance.php
- Type: PHP
- Size: 12.4 KB
- Path: .
- Name: finance.php
------------------------------------------------------------
requireLogin();
$page_title = 'Finance Management';
$error_message = '';
// Test database connection and table existence
try {
// Test if tables exist
$tables_check = [];
$stmt = $pdo->query("SHOW TABLES LIKE 'investments'");
$tables_check['investments'] = $stmt->rowCount() > 0;
$stmt = $pdo->query("SHOW TABLES LIKE 'withdrawals'");
$tables_check['withdrawals'] = $stmt->rowCount() > 0;
$stmt = $pdo->query("SHOW TABLES LIKE 'emi_schedule'");
$tables_check['emi_schedule'] = $stmt->rowCount() > 0;
$stmt = $pdo->query("SHOW TABLES LIKE 'loan_payments'");
$tables_check['loan_payments'] = $stmt->rowCount() > 0;
} catch (Exception $e) {
$error_message = 'Database connection error: ' . $e->getMessage();
$tables_check = [];
}
include 'includes/header.php';
?>
| Database Table |
Status |
Action Required |
'Store investment records',
'withdrawals' => 'Store withdrawal records',
'emi_schedule' => 'Store EMI payment schedules',
'loan_payments' => 'Store loan payment history'
];
$all_tables_exist = true;
foreach ($required_tables as $table => $description):
$exists = $tables_check[$table] ?? false;
if (!$exists) $all_tables_exist = false;
?>
|
✅ EXISTS
❌ MISSING
|
Create table using SQL script
Ready to use
|
Database Tables Missing
The finance system requires additional database tables to function properly.
Please follow these steps to set up the database:
Step 1: Access your database
Log into phpMyAdmin or your database management tool
Step 2: Run the SQL script
Execute the following SQL commands:
Step 3: Refresh this page
After running the SQL script, refresh this page to continue with the finance setup.
Finance System Initialized
All required database tables are present. The finance system is ready to use!
1. Database Setup
Create the required database tables using the SQL script above.
2. Add Investments
Record your business investments and capital contributions.
3. Track Loans
Manage EMI loans and interest-based loans with payment tracking.
4. Balance Reports
Generate comprehensive financial statements and profit/loss reports.
| PHP Version |
|
| Database Connection |
|
| Current User |
getUserName()); ?> (getUserRole()); ?>) |
| Server Time |
|
-------------------- END OF FILE --------------------
### FILE 6: index.php
- Type: PHP
- Size: 6.06 KB
- Path: .
- Name: index.php
------------------------------------------------------------
requireLogin();
$page_title = 'Dashboard';
// Fetch real statistics
try {
// Sales statistics
$stmt = $pdo->prepare("
SELECT
COUNT(*) as total_sales_count,
COALESCE(SUM(total_amount), 0) as total_sales_amount,
COALESCE(SUM(CASE WHEN payment_status = 'pending' THEN total_amount ELSE 0 END), 0) as pending_amount
FROM sales
");
$stmt->execute();
$sales_stats = $stmt->fetch(PDO::FETCH_ASSOC);
// Products/Services count
$stmt = $pdo->prepare("SELECT COUNT(*) as count FROM products_services WHERE status = 'active'");
$stmt->execute();
$products_count = $stmt->fetchColumn();
// Recent sales for activity - now showing bills with their items
$stmt = $pdo->prepare("
SELECT s.*,
(SELECT GROUP_CONCAT(CONCAT(ps.name, ' (', si.quantity, ' ', u.unit_symbol, ')') SEPARATOR ', ')
FROM sales_items si
JOIN products_services ps ON si.product_service_id = ps.id
JOIN units u ON ps.unit_id = u.id
WHERE si.sale_id = s.id) as items_summary
FROM sales s
ORDER BY s.created_at DESC
LIMIT 5
");
$stmt->execute();
$recent_sales = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$sales_stats = ['total_sales_count' => 0, 'total_sales_amount' => 0, 'pending_amount' => 0];
$products_count = 0;
$recent_sales = [];
}
include 'includes/header.php';
?>
| Bill# |
Date |
Items |
Amount |
Customer |
|
|
|
₹ |
-
|
| User Role |
getUserRole())); ?> |
| Login Time |
|
| System Status |
Active |
-------------------- END OF FILE --------------------
### FILE 7: loan_calculator.php
- Type: PHP
- Size: 18.21 KB
- Path: .
- Name: loan_calculator.php
------------------------------------------------------------
requireLogin();
$page_title = 'Loan Calculator';
include 'includes/header.php';
?>
| EMI # |
EMI Date |
Opening Balance |
EMI Amount |
Principal |
Interest |
Closing Balance |
-------------------- END OF FILE --------------------
### FILE 8: login.php
- Type: PHP
- Size: 2.57 KB
- Path: .
- Name: login.php
------------------------------------------------------------
isSetupCompleted()) {
header("Location: setup.php");
exit();
}
// Redirect if already logged in
if ($auth->isLoggedIn()) {
header("Location: index.php");
exit();
}
$error_message = '';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if (empty($username) || empty($password)) {
$error_message = 'Please enter both username and password.';
} else {
if ($auth->login($username, $password)) {
header("Location: index.php");
exit();
} else {
$error_message = 'Invalid username or password.';
}
}
}
$page_title = 'Login';
?>
- Kayal Aqua Management
-------------------- END OF FILE --------------------
### FILE 9: logout.php
- Type: PHP
- Size: 50 B
- Path: .
- Name: logout.php
------------------------------------------------------------
logout();
?>
-------------------- END OF FILE --------------------
### FILE 10: payment_slip.php
- Type: PHP
- Size: 16.17 KB
- Path: .
- Name: payment_slip.php
------------------------------------------------------------
requireLogin();
// Get expense ID from URL
$expense_id = $_GET['expense_id'] ?? 0;
if (!$expense_id) {
header("Location: expenses.php");
exit();
}
try {
// Fetch expense details
$stmt = $pdo->prepare("
SELECT e.*, ec.category_name,
COALESCE(es.subcategory_name, '-') as subcategory_name,
u.full_name as paid_by_name, uc.full_name as created_by_name
FROM expenses e
JOIN expense_categories ec ON e.category_id = ec.id
LEFT JOIN expense_subcategories es ON e.subcategory_id = es.id
JOIN users u ON e.paid_by = u.id
JOIN users uc ON e.created_by = uc.id
WHERE e.id = ?
");
$stmt->execute([$expense_id]);
$expense = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$expense) {
header("Location: expenses.php");
exit();
}
} catch (PDOException $e) {
header("Location: expenses.php");
exit();
}
// Format payment method
$payment_methods = [
'cash' => 'Cash',
'upi' => 'UPI',
'credit_card' => 'Credit Card'
];
$payment_method_display = $payment_methods[$expense['paid_through']] ?? ucfirst($expense['paid_through']);
?>
Payment Slip -
PAYMENT SLIP
Expense Details
Expense Number:
Date:
Time:
Category:
Subcategory:
Created By:
Payment Details
Paid To:
Paid By:
Payment Method:
Status:
PAID
TOTAL AMOUNT PAID
₹
Amount in words: 0) {
$words .= ' and ' . numberToWords($paisa) . ' Paisa';
}
$words .= ' Only';
echo $words;
?>
Notes:
-------------------- END OF FILE --------------------
### FILE 11: sales.php
- Type: PHP
- Size: 46.04 KB
- Path: .
- Name: sales.php
------------------------------------------------------------
requireLogin();
$page_title = 'Sales Management';
$success_message = '';
$error_message = '';
// Get edit data if editing
$edit_sale = null;
$edit_sale_items = [];
if (isset($_GET['edit_sale'])) {
try {
$edit_stmt = $pdo->prepare("SELECT * FROM sales WHERE id = ?");
$edit_stmt->execute([$_GET['edit_sale']]);
$edit_sale = $edit_stmt->fetch(PDO::FETCH_ASSOC);
if ($edit_sale) {
$edit_items_stmt = $pdo->prepare("
SELECT si.*, ps.name as product_name
FROM sales_items si
JOIN products_services ps ON si.product_service_id = ps.id
WHERE si.sale_id = ?
");
$edit_items_stmt->execute([$edit_sale['id']]);
$edit_sale_items = $edit_items_stmt->fetchAll(PDO::FETCH_ASSOC);
}
} catch (PDOException $e) {
$error_message = 'Error fetching sale for editing.';
}
}
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
if ($_POST['action'] == 'record_sale' || $_POST['action'] == 'edit_sale') {
$sale_id = $_POST['sale_id'] ?? null;
$sale_date = $_POST['sale_date'] ?? '';
$customer_name = trim($_POST['customer_name'] ?? '');
$customer_phone = trim($_POST['customer_phone'] ?? '');
$payment_status = $_POST['payment_status'] ?? 'paid';
$payment_method = $_POST['payment_method'] ?? 'cash';
$notes = trim($_POST['notes'] ?? '');
// Get line items from individual form fields
$products = $_POST['product_id'] ?? [];
$quantities = $_POST['quantity'] ?? [];
$unit_prices = $_POST['unit_price'] ?? [];
$line_totals = $_POST['line_total'] ?? [];
// Validation
if (empty($sale_date)) {
$error_message = 'Sale date is required.';
} elseif (empty($products) || !is_array($products)) {
$error_message = 'At least one product/service must be added.';
} else {
// Build line items array
$line_items = [];
$total_amount = 0;
for ($i = 0; $i < count($products); $i++) {
if (!empty($products[$i]) && !empty($quantities[$i]) && isset($unit_prices[$i]) && isset($line_totals[$i])) {
$product_id = intval($products[$i]);
$quantity = floatval($quantities[$i]);
$unit_price = floatval($unit_prices[$i]);
$line_total = floatval($line_totals[$i]);
if ($product_id > 0 && $quantity > 0 && $unit_price >= 0 && $line_total >= 0) {
$line_items[] = [
'product_id' => $product_id,
'quantity' => $quantity,
'unit_price' => $unit_price,
'line_total' => $line_total
];
$total_amount += $line_total;
}
}
}
if (empty($line_items)) {
$error_message = 'At least one valid product/service must be added.';
} elseif ($total_amount <= 0) {
$error_message = 'Total amount must be greater than zero.';
} else {
try {
// Start transaction
$pdo->beginTransaction();
if ($_POST['action'] == 'edit_sale' && $sale_id) {
// Update existing sale
$sale_stmt = $pdo->prepare("
UPDATE sales SET sale_date = ?, customer_name = ?, customer_phone = ?,
total_amount = ?, payment_status = ?, payment_method = ?, notes = ?
WHERE id = ?
");
$sale_stmt->execute([
$sale_date, $customer_name, $customer_phone,
$total_amount, $payment_status, $payment_method, $notes, $sale_id
]);
// Delete existing line items
$delete_items_stmt = $pdo->prepare("DELETE FROM sales_items WHERE sale_id = ?");
$delete_items_stmt->execute([$sale_id]);
$action_message = 'Sale updated successfully!';
} else {
// Create new sale
$bill_number = 'BILL' . date('YmdHis') . rand(100, 999);
$sale_stmt = $pdo->prepare("
INSERT INTO sales (bill_number, sale_date, customer_name, customer_phone,
total_amount, payment_status, payment_method, notes, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$sale_stmt->execute([
$bill_number, $sale_date, $customer_name, $customer_phone,
$total_amount, $payment_status, $payment_method, $notes, $_SESSION['user_id']
]);
$sale_id = $pdo->lastInsertId();
$action_message = 'Sale recorded successfully! Bill: ' . $bill_number;
}
// Insert line items
$item_stmt = $pdo->prepare("
INSERT INTO sales_items (sale_id, product_service_id, quantity, unit_price, line_total)
VALUES (?, ?, ?, ?, ?)
");
foreach ($line_items as $item) {
$item_stmt->execute([
$sale_id, $item['product_id'], $item['quantity'],
$item['unit_price'], $item['line_total']
]);
}
$pdo->commit();
$_SESSION['success_message'] = $action_message;
// Redirect to prevent form resubmission
header("Location: sales.php");
exit();
} catch (Exception $e) {
$pdo->rollBack();
$error_message = 'Error processing sale: ' . $e->getMessage();
}
}
}
}
elseif ($_POST['action'] == 'delete_sale') {
$sale_id = intval($_POST['sale_id'] ?? 0);
if ($sale_id > 0) {
try {
$pdo->beginTransaction();
// Delete sales items first
$delete_items_stmt = $pdo->prepare("DELETE FROM sales_items WHERE sale_id = ?");
$delete_items_stmt->execute([$sale_id]);
// Delete sale
$delete_sale_stmt = $pdo->prepare("DELETE FROM sales WHERE id = ?");
$result = $delete_sale_stmt->execute([$sale_id]);
if ($result) {
$pdo->commit();
$_SESSION['success_message'] = 'Sale deleted successfully.';
} else {
$pdo->rollBack();
$_SESSION['error_message'] = 'Failed to delete sale.';
}
} catch (Exception $e) {
$pdo->rollBack();
$_SESSION['error_message'] = 'Error deleting sale: ' . $e->getMessage();
}
}
header("Location: sales.php");
exit();
}
elseif ($_POST['action'] == 'export_sales') {
$export_detailed = $_POST['export_detailed'] ?? '0';
$export_dashboard = $_POST['export_dashboard'] ?? '0';
$export_filter_date_from = $_POST['export_filter_date_from'] ?? '';
$export_filter_date_to = $_POST['export_filter_date_to'] ?? '';
$export_filter_product = $_POST['export_filter_product'] ?? '';
// Build export query with same filters
$export_where_clause = "1=1";
$export_params = [];
if (!empty($export_filter_date_from)) {
$export_where_clause .= " AND s.sale_date >= ?";
$export_params[] = $export_filter_date_from;
}
if (!empty($export_filter_date_to)) {
$export_where_clause .= " AND s.sale_date <= ?";
$export_params[] = $export_filter_date_to;
}
if (!empty($export_filter_product)) {
$export_where_clause .= " AND EXISTS (SELECT 1 FROM sales_items si WHERE si.sale_id = s.id AND si.product_service_id = ?)";
$export_params[] = $export_filter_product;
}
try {
if ($export_dashboard == '1') {
// Export dashboard - product-wise summary
$export_stmt = $pdo->prepare("
SELECT ps.name as product_name,
un.unit_symbol,
COUNT(DISTINCT s.id) as total_bills,
SUM(si.quantity) as total_quantity,
AVG(si.unit_price) as avg_unit_price,
SUM(si.line_total) as total_sales_amount
FROM sales s
JOIN sales_items si ON s.id = si.sale_id
JOIN products_services ps ON si.product_service_id = ps.id
JOIN units un ON ps.unit_id = un.id
WHERE $export_where_clause
GROUP BY ps.id, ps.name, un.unit_symbol
ORDER BY total_sales_amount DESC
");
$export_stmt->execute($export_params);
$export_data = $export_stmt->fetchAll(PDO::FETCH_ASSOC);
// Get totals
$totals_stmt = $pdo->prepare("
SELECT COUNT(DISTINCT s.id) as total_bills,
SUM(s.total_amount) as grand_total
FROM sales s
WHERE $export_where_clause
");
$totals_stmt->execute($export_params);
$totals = $totals_stmt->fetch(PDO::FETCH_ASSOC);
$filename = 'sales_dashboard_' . date('Y-m-d_H-i-s') . '.csv';
// Generate CSV
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$output = fopen('php://output', 'w');
// Dashboard header
fputcsv($output, ['SALES DASHBOARD REPORT']);
fputcsv($output, ['Generated on: ' . date('F j, Y g:i A')]);
if (!empty($export_filter_date_from) || !empty($export_filter_date_to)) {
$period = 'Period: ';
if (!empty($export_filter_date_from)) $period .= 'From ' . date('M j, Y', strtotime($export_filter_date_from));
if (!empty($export_filter_date_to)) $period .= ' To ' . date('M j, Y', strtotime($export_filter_date_to));
fputcsv($output, [$period]);
}
if (!empty($export_filter_product)) {
$product_name = '';
foreach ($products_services as $ps) {
if ($ps['id'] == $export_filter_product) {
$product_name = $ps['name'];
break;
}
}
fputcsv($output, ['Product Filter: ' . $product_name]);
}
fputcsv($output, ['']); // Empty row
// Summary totals
fputcsv($output, ['SUMMARY']);
fputcsv($output, ['Total Bills', $totals['total_bills']]);
fputcsv($output, ['Grand Total Sales', number_format($totals['grand_total'], 2)]);
fputcsv($output, ['']); // Empty row
// Product-wise details
fputcsv($output, ['PRODUCT-WISE SALES BREAKDOWN']);
fputcsv($output, ['Product Name', 'Unit', 'Total Bills', 'Total Quantity', 'Avg Unit Price', 'Total Sales Amount']);
foreach ($export_data as $row) {
fputcsv($output, [
$row['product_name'],
$row['unit_symbol'],
$row['total_bills'],
number_format($row['total_quantity'], 3),
number_format($row['avg_unit_price'], 2),
number_format($row['total_sales_amount'], 2)
]);
}
fclose($output);
exit();
} elseif ($export_detailed == '1') {
// Export detailed view
$export_stmt = $pdo->prepare("
SELECT s.bill_number, s.sale_date, s.customer_name, s.customer_phone,
ps.name as product_name, si.quantity, un.unit_symbol,
si.unit_price, si.line_total, s.payment_status, s.payment_method, s.notes
FROM sales s
JOIN sales_items si ON s.id = si.sale_id
JOIN products_services ps ON si.product_service_id = ps.id
JOIN units un ON ps.unit_id = un.id
WHERE $export_where_clause
ORDER BY s.created_at DESC, s.id, si.id
");
$export_stmt->execute($export_params);
$export_data = $export_stmt->fetchAll(PDO::FETCH_ASSOC);
$filename = 'sales_detailed_' . date('Y-m-d_H-i-s') . '.csv';
$headers = ['Bill Number', 'Date', 'Customer Name', 'Customer Phone', 'Product', 'Quantity', 'Unit', 'Unit Price', 'Line Total', 'Payment Status', 'Payment Method', 'Notes'];
} else {
// Export summary view
$export_stmt = $pdo->prepare("
SELECT s.bill_number, s.sale_date, s.customer_name, s.customer_phone,
s.total_amount, s.payment_status, s.payment_method, s.notes,
GROUP_CONCAT(CONCAT(ps.name, ' (', si.quantity, ' ', un.unit_symbol, ')') SEPARATOR ', ') as items
FROM sales s
LEFT JOIN sales_items si ON s.id = si.sale_id
LEFT JOIN products_services ps ON si.product_service_id = ps.id
LEFT JOIN units un ON ps.unit_id = un.id
WHERE $export_where_clause
GROUP BY s.id
ORDER BY s.created_at DESC
");
$export_stmt->execute($export_params);
$export_data = $export_stmt->fetchAll(PDO::FETCH_ASSOC);
$filename = 'sales_summary_' . date('Y-m-d_H-i-s') . '.csv';
$headers = ['Bill Number', 'Date', 'Customer Name', 'Customer Phone', 'Items', 'Total Amount', 'Payment Status', 'Payment Method', 'Notes'];
}
if ($export_dashboard != '1') {
// Generate CSV for detailed/summary views
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
$output = fopen('php://output', 'w');
fputcsv($output, $headers);
foreach ($export_data as $row) {
if ($export_detailed == '1') {
fputcsv($output, [
$row['bill_number'],
$row['sale_date'],
$row['customer_name'],
$row['customer_phone'],
$row['product_name'],
$row['quantity'],
$row['unit_symbol'],
$row['unit_price'],
$row['line_total'],
$row['payment_status'],
$row['payment_method'],
$row['notes']
]);
} else {
fputcsv($output, [
$row['bill_number'],
$row['sale_date'],
$row['customer_name'],
$row['customer_phone'],
$row['items'],
$row['total_amount'],
$row['payment_status'],
$row['payment_method'],
$row['notes']
]);
}
}
fclose($output);
exit();
}
} catch (PDOException $e) {
$error_message = 'Error exporting data: ' . $e->getMessage();
}
}
}
// Fetch products/services
try {
$products_stmt = $pdo->prepare("
SELECT ps.*, u.unit_name, u.unit_symbol
FROM products_services ps
JOIN units u ON ps.unit_id = u.id
WHERE ps.status = 'active'
ORDER BY ps.name
");
$products_stmt->execute();
$products_services = $products_stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$products_services = [];
}
// Fetch sales with filters
$filter_date_from = $_GET['filter_date_from'] ?? '';
$filter_date_to = $_GET['filter_date_to'] ?? '';
$filter_product = $_GET['filter_product'] ?? '';
$detailed_view = $_GET['detailed_view'] ?? '0';
$where_clause = "1=1";
$params = [];
if (!empty($filter_date_from)) {
$where_clause .= " AND s.sale_date >= ?";
$params[] = $filter_date_from;
}
if (!empty($filter_date_to)) {
$where_clause .= " AND s.sale_date <= ?";
$params[] = $filter_date_to;
}
if (!empty($filter_product)) {
$where_clause .= " AND EXISTS (SELECT 1 FROM sales_items si WHERE si.sale_id = s.id AND si.product_service_id = ?)";
$params[] = $filter_product;
}
try {
// Get sales stats
$stats_stmt = $pdo->prepare("
SELECT
COUNT(*) as total_sales,
COALESCE(SUM(total_amount), 0) as total_revenue,
COALESCE(SUM(CASE WHEN payment_status = 'paid' THEN total_amount ELSE 0 END), 0) as paid_amount
FROM sales s WHERE $where_clause
");
$stats_stmt->execute($params);
$stats = $stats_stmt->fetch(PDO::FETCH_ASSOC);
if ($detailed_view == '1') {
// Get detailed view - each line item as separate row
$sales_stmt = $pdo->prepare("
SELECT s.*, u.full_name as created_by_name,
si.quantity, si.unit_price, si.line_total,
ps.name as product_name, un.unit_symbol
FROM sales s
JOIN users u ON s.created_by = u.id
JOIN sales_items si ON s.id = si.sale_id
JOIN products_services ps ON si.product_service_id = ps.id
JOIN units un ON ps.unit_id = un.id
WHERE $where_clause
ORDER BY s.created_at DESC, s.id, si.id
LIMIT 200
");
$sales_stmt->execute($params);
$sales = $sales_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
// Get sales records with items (summary view)
$sales_stmt = $pdo->prepare("
SELECT s.*, u.full_name as created_by_name,
(SELECT GROUP_CONCAT(
CONCAT(ps.name, ': ', si.quantity, ' ', u.unit_symbol, ' @ ₹', si.unit_price, ' = ₹', si.line_total)
SEPARATOR '|'
)
FROM sales_items si
JOIN products_services ps ON si.product_service_id = ps.id
JOIN units u ON ps.unit_id = u.id
WHERE si.sale_id = s.id) as items_summary
FROM sales s
JOIN users u ON s.created_by = u.id
WHERE $where_clause
ORDER BY s.created_at DESC
LIMIT 50
");
$sales_stmt->execute($params);
$sales = $sales_stmt->fetchAll(PDO::FETCH_ASSOC);
}
} catch (PDOException $e) {
$stats = ['total_sales' => 0, 'total_revenue' => 0, 'paid_amount' => 0];
$sales = [];
$error_message = 'Error fetching sales data.';
}
include 'includes/header.php';
?>
No Sales Found
Record your first sale using the form above.
| Bill# |
Date |
Product |
Quantity |
Unit Price |
Line Total |
Items |
Total |
Customer |
Payment |
Actions |
|
|
|
|
₹ |
₹ |
' . htmlspecialchars($item) . '';
}
} else {
echo ' No items ';
}
?>
|
₹ |
-
|
|
|
|
Notes:
|
-------------------- END OF FILE --------------------
### FILE 12: settings.php
- Type: PHP
- Size: 42.85 KB
- Path: .
- Name: settings.php
------------------------------------------------------------
requireLogin();
$page_title = 'Settings';
$success_message = '';
$error_message = '';
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
try {
switch ($_POST['action']) {
case 'create_unit':
$unit_name = trim($_POST['unit_name'] ?? '');
$unit_symbol = trim($_POST['unit_symbol'] ?? '');
$unit_type = $_POST['unit_type'] ?? '';
if (empty($unit_name) || empty($unit_symbol) || empty($unit_type)) {
$error_message = 'All fields are required for unit creation.';
} else {
$stmt = $pdo->prepare("SELECT id FROM units WHERE unit_name = ? OR unit_symbol = ?");
$stmt->execute([$unit_name, $unit_symbol]);
if ($stmt->fetch()) {
$error_message = 'Unit name or symbol already exists.';
} else {
$stmt = $pdo->prepare("INSERT INTO units (unit_name, unit_symbol, unit_type, created_by) VALUES (?, ?, ?, ?)");
if ($stmt->execute([$unit_name, $unit_symbol, $unit_type, $_SESSION['user_id']])) {
$_SESSION['success_message'] = 'Unit created successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to create unit.';
}
}
}
break;
case 'edit_unit':
$unit_id = $_POST['unit_id'] ?? 0;
$unit_name = trim($_POST['unit_name'] ?? '');
$unit_symbol = trim($_POST['unit_symbol'] ?? '');
$unit_type = $_POST['unit_type'] ?? '';
if (empty($unit_name) || empty($unit_symbol) || empty($unit_type)) {
$error_message = 'All fields are required for unit update.';
} else {
$stmt = $pdo->prepare("SELECT id FROM units WHERE (unit_name = ? OR unit_symbol = ?) AND id != ?");
$stmt->execute([$unit_name, $unit_symbol, $unit_id]);
if ($stmt->fetch()) {
$error_message = 'Unit name or symbol already exists.';
} else {
$stmt = $pdo->prepare("UPDATE units SET unit_name = ?, unit_symbol = ?, unit_type = ? WHERE id = ?");
if ($stmt->execute([$unit_name, $unit_symbol, $unit_type, $unit_id])) {
$_SESSION['success_message'] = 'Unit updated successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to update unit.';
}
}
}
break;
case 'delete_unit':
$unit_id = $_POST['unit_id'] ?? 0;
// Check if unit is being used by any products/services
$stmt = $pdo->prepare("SELECT COUNT(*) FROM products_services WHERE unit_id = ?");
$stmt->execute([$unit_id]);
$usage_count = $stmt->fetchColumn();
if ($usage_count > 0) {
$error_message = 'Cannot delete unit. It is being used by ' . $usage_count . ' product(s)/service(s).';
} else {
$stmt = $pdo->prepare("DELETE FROM units WHERE id = ?");
if ($stmt->execute([$unit_id])) {
$_SESSION['success_message'] = 'Unit deleted successfully.';
} else {
$_SESSION['error_message'] = 'Failed to delete unit.';
}
header("Location: settings.php");
exit();
}
break;
case 'create_expense_category':
$category_name = trim($_POST['category_name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($category_name)) {
$error_message = 'Category name is required.';
} else {
$stmt = $pdo->prepare("SELECT id FROM expense_categories WHERE category_name = ?");
$stmt->execute([$category_name]);
if ($stmt->fetch()) {
$error_message = 'Category already exists.';
} else {
$stmt = $pdo->prepare("INSERT INTO expense_categories (category_name, description, created_by) VALUES (?, ?, ?)");
if ($stmt->execute([$category_name, $description, $_SESSION['user_id']])) {
$_SESSION['success_message'] = 'Expense category created successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to create category.';
}
}
}
break;
case 'edit_expense_category':
$category_id = $_POST['category_id'] ?? 0;
$category_name = trim($_POST['category_name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($category_name)) {
$error_message = 'Category name is required.';
} else {
$stmt = $pdo->prepare("SELECT id FROM expense_categories WHERE category_name = ? AND id != ?");
$stmt->execute([$category_name, $category_id]);
if ($stmt->fetch()) {
$error_message = 'Category name already exists.';
} else {
$stmt = $pdo->prepare("UPDATE expense_categories SET category_name = ?, description = ? WHERE id = ?");
if ($stmt->execute([$category_name, $description, $category_id])) {
$_SESSION['success_message'] = 'Category updated successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to update category.';
}
}
}
break;
case 'delete_expense_category':
$category_id = $_POST['category_id'] ?? 0;
// Check if category has subcategories
$stmt = $pdo->prepare("SELECT COUNT(*) FROM expense_subcategories WHERE category_id = ?");
$stmt->execute([$category_id]);
$subcategory_count = $stmt->fetchColumn();
if ($subcategory_count > 0) {
$error_message = 'Cannot delete category. It has ' . $subcategory_count . ' subcategory/subcategories.';
} else {
$stmt = $pdo->prepare("DELETE FROM expense_categories WHERE id = ?");
if ($stmt->execute([$category_id])) {
$_SESSION['success_message'] = 'Category deleted successfully.';
} else {
$_SESSION['error_message'] = 'Failed to delete category.';
}
header("Location: settings.php");
exit();
}
break;
case 'create_expense_subcategory':
$category_id = $_POST['category_id'] ?? 0;
$subcategory_name = trim($_POST['subcategory_name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($subcategory_name) || empty($category_id)) {
$error_message = 'Subcategory name and category are required.';
} else {
$stmt = $pdo->prepare("SELECT id FROM expense_subcategories WHERE category_id = ? AND subcategory_name = ?");
$stmt->execute([$category_id, $subcategory_name]);
if ($stmt->fetch()) {
$error_message = 'Subcategory already exists in this category.';
} else {
$stmt = $pdo->prepare("INSERT INTO expense_subcategories (category_id, subcategory_name, description, created_by) VALUES (?, ?, ?, ?)");
if ($stmt->execute([$category_id, $subcategory_name, $description, $_SESSION['user_id']])) {
$_SESSION['success_message'] = 'Expense subcategory created successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to create subcategory.';
}
}
}
break;
case 'edit_expense_subcategory':
$subcategory_id = $_POST['subcategory_id'] ?? 0;
$category_id = $_POST['category_id'] ?? 0;
$subcategory_name = trim($_POST['subcategory_name'] ?? '');
$description = trim($_POST['description'] ?? '');
if (empty($subcategory_name) || empty($category_id)) {
$error_message = 'Subcategory name and category are required.';
} else {
$stmt = $pdo->prepare("SELECT id FROM expense_subcategories WHERE category_id = ? AND subcategory_name = ? AND id != ?");
$stmt->execute([$category_id, $subcategory_name, $subcategory_id]);
if ($stmt->fetch()) {
$error_message = 'Subcategory already exists in this category.';
} else {
$stmt = $pdo->prepare("UPDATE expense_subcategories SET category_id = ?, subcategory_name = ?, description = ? WHERE id = ?");
if ($stmt->execute([$category_id, $subcategory_name, $description, $subcategory_id])) {
$_SESSION['success_message'] = 'Subcategory updated successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to update subcategory.';
}
}
}
break;
case 'delete_expense_subcategory':
$subcategory_id = $_POST['subcategory_id'] ?? 0;
$stmt = $pdo->prepare("DELETE FROM expense_subcategories WHERE id = ?");
if ($stmt->execute([$subcategory_id])) {
$_SESSION['success_message'] = 'Subcategory deleted successfully.';
} else {
$_SESSION['error_message'] = 'Failed to delete subcategory.';
}
header("Location: settings.php");
exit();
break;
case 'create_product_service':
$name = trim($_POST['name'] ?? '');
$type = $_POST['type'] ?? '';
$description = trim($_POST['description'] ?? '');
$unit_id = $_POST['unit_id'] ?? 0;
$unit_rate = $_POST['unit_rate'] ?? 0;
if (empty($name) || empty($type) || empty($unit_id)) {
$error_message = 'Name, type, and unit are required.';
} elseif (!is_numeric($unit_rate) || $unit_rate < 0) {
$error_message = 'Please enter a valid unit rate.';
} else {
$stmt = $pdo->prepare("SELECT id FROM products_services WHERE name = ?");
$stmt->execute([$name]);
if ($stmt->fetch()) {
$error_message = 'Product/Service name already exists.';
} else {
$stmt = $pdo->prepare("INSERT INTO products_services (name, type, description, unit_id, unit_rate, created_by) VALUES (?, ?, ?, ?, ?, ?)");
if ($stmt->execute([$name, $type, $description, $unit_id, $unit_rate, $_SESSION['user_id']])) {
$_SESSION['success_message'] = ucfirst($type) . ' created successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to create ' . $type . '.';
}
}
}
break;
case 'edit_product_service':
$ps_id = $_POST['ps_id'] ?? 0;
$name = trim($_POST['name'] ?? '');
$type = $_POST['type'] ?? '';
$description = trim($_POST['description'] ?? '');
$unit_id = $_POST['unit_id'] ?? 0;
$unit_rate = $_POST['unit_rate'] ?? 0;
if (empty($name) || empty($type) || empty($unit_id)) {
$error_message = 'Name, type, and unit are required.';
} elseif (!is_numeric($unit_rate) || $unit_rate < 0) {
$error_message = 'Please enter a valid unit rate.';
} else {
$stmt = $pdo->prepare("SELECT id FROM products_services WHERE name = ? AND id != ?");
$stmt->execute([$name, $ps_id]);
if ($stmt->fetch()) {
$error_message = 'Product/Service name already exists.';
} else {
$stmt = $pdo->prepare("UPDATE products_services SET name = ?, type = ?, description = ?, unit_id = ?, unit_rate = ? WHERE id = ?");
if ($stmt->execute([$name, $type, $description, $unit_id, $unit_rate, $ps_id])) {
$_SESSION['success_message'] = 'Product/Service updated successfully.';
header("Location: settings.php");
exit();
} else {
$error_message = 'Failed to update product/service.';
}
}
}
break;
case 'delete_product_service':
$ps_id = $_POST['ps_id'] ?? 0;
$stmt = $pdo->prepare("DELETE FROM products_services WHERE id = ?");
if ($stmt->execute([$ps_id])) {
$_SESSION['success_message'] = 'Product/Service deleted successfully.';
} else {
$_SESSION['error_message'] = 'Failed to delete product/service.';
}
header("Location: settings.php");
exit();
break;
case 'toggle_unit_status':
$unit_id = $_POST['unit_id'] ?? 0;
$status = $_POST['status'] ?? 'active';
$stmt = $pdo->prepare("UPDATE units SET status = ? WHERE id = ?");
if ($stmt->execute([$status, $unit_id])) {
$_SESSION['success_message'] = 'Unit status updated successfully.';
} else {
$_SESSION['error_message'] = 'Failed to update unit status.';
}
header("Location: settings.php");
exit();
break;
case 'toggle_category_status':
$category_id = $_POST['category_id'] ?? 0;
$status = $_POST['status'] ?? 'active';
$stmt = $pdo->prepare("UPDATE expense_categories SET status = ? WHERE id = ?");
if ($stmt->execute([$status, $category_id])) {
$_SESSION['success_message'] = 'Category status updated successfully.';
} else {
$_SESSION['error_message'] = 'Failed to update category status.';
}
header("Location: settings.php");
exit();
break;
case 'toggle_product_status':
$product_id = $_POST['product_id'] ?? 0;
$status = $_POST['status'] ?? 'active';
$stmt = $pdo->prepare("UPDATE products_services SET status = ? WHERE id = ?");
if ($stmt->execute([$status, $product_id])) {
$_SESSION['success_message'] = 'Product/Service status updated successfully.';
} else {
$_SESSION['error_message'] = 'Failed to update status.';
}
header("Location: settings.php");
exit();
break;
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
// Get edit data if editing
$edit_unit = null;
$edit_category = null;
$edit_subcategory = null;
$edit_ps = null;
if (isset($_GET['edit_unit'])) {
$stmt = $pdo->prepare("SELECT * FROM units WHERE id = ?");
$stmt->execute([$_GET['edit_unit']]);
$edit_unit = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (isset($_GET['edit_category'])) {
$stmt = $pdo->prepare("SELECT * FROM expense_categories WHERE id = ?");
$stmt->execute([$_GET['edit_category']]);
$edit_category = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (isset($_GET['edit_subcategory'])) {
$stmt = $pdo->prepare("SELECT * FROM expense_subcategories WHERE id = ?");
$stmt->execute([$_GET['edit_subcategory']]);
$edit_subcategory = $stmt->fetch(PDO::FETCH_ASSOC);
}
if (isset($_GET['edit_ps'])) {
$stmt = $pdo->prepare("SELECT * FROM products_services WHERE id = ?");
$stmt->execute([$_GET['edit_ps']]);
$edit_ps = $stmt->fetch(PDO::FETCH_ASSOC);
}
// Fetch data for display
try {
// Fetch units
$stmt = $pdo->prepare("SELECT * FROM units ORDER BY unit_type, unit_name");
$stmt->execute();
$units = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch expense categories
$stmt = $pdo->prepare("SELECT * FROM expense_categories ORDER BY category_name");
$stmt->execute();
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch expense subcategories with category names
$stmt = $pdo->prepare("
SELECT es.*, ec.category_name
FROM expense_subcategories es
JOIN expense_categories ec ON es.category_id = ec.id
ORDER BY ec.category_name, es.subcategory_name
");
$stmt->execute();
$subcategories = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Fetch products/services with unit info
$stmt = $pdo->prepare("
SELECT ps.*, u.unit_name, u.unit_symbol
FROM products_services ps
JOIN units u ON ps.unit_id = u.id
ORDER BY ps.type, ps.name
");
$stmt->execute();
$products_services = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$error_message = 'Failed to fetch data: ' . $e->getMessage();
$units = $categories = $subcategories = $products_services = [];
}
include 'includes/header.php';
?>
| Name |
Symbol |
Type |
Status |
Actions |
|
|
|
|
|
| Category Name |
Description |
Status |
Actions |
|
|
|
|
| Main Category |
Subcategory |
Description |
Status |
Actions |
|
|
|
|
|
| Name |
Type |
Unit |
Rate (₹) |
Description |
Status |
Actions |
|
|
|
₹ |
|
|
|
-------------------- END OF FILE --------------------
### FILE 13: setup.php
- Type: PHP
- Size: 5 KB
- Path: .
- Name: setup.php
------------------------------------------------------------
isSetupCompleted()) {
header("Location: login.php");
exit();
}
$error_message = '';
$success_message = '';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$full_name = trim($_POST['full_name'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
// Validation
if (empty($full_name) || empty($phone) || empty($username) || empty($password)) {
$error_message = 'All fields are required.';
} elseif ($password !== $confirm_password) {
$error_message = 'Passwords do not match.';
} elseif (strlen($password) < 6) {
$error_message = 'Password must be at least 6 characters long.';
} elseif (!preg_match('/^[0-9+\-\s]{10,15}$/', $phone)) {
$error_message = 'Please enter a valid phone number.';
} elseif (!preg_match('/^[a-zA-Z0-9_]{3,}$/', $username)) {
$error_message = 'Username must be at least 3 characters and contain only letters, numbers, and underscore.';
} else {
// Create first admin user
if ($auth->createFirstAdmin($full_name, $phone, $username, $password)) {
$success_message = 'Admin user created successfully! You can now login.';
} else {
$error_message = 'Failed to create admin user. Please try again.';
}
}
}
$page_title = 'Initial Setup';
?>
- Kayal Aqua Management
-------------------- END OF FILE --------------------
### FILE 14: users.php
- Type: PHP
- Size: 9.41 KB
- Path: .
- Name: users.php
------------------------------------------------------------
requireAdmin(); // Only admin can access this page
$page_title = 'Users Management';
$success_message = '';
$error_message = '';
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create_user':
$full_name = trim($_POST['full_name'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$role = $_POST['role'] ?? 'manager';
// Validation
if (empty($full_name) || empty($phone) || empty($username) || empty($password)) {
$error_message = 'All fields are required.';
} elseif (strlen($password) < 6) {
$error_message = 'Password must be at least 6 characters long.';
} elseif (!preg_match('/^[0-9+\-\s]{10,15}$/', $phone)) {
$error_message = 'Please enter a valid phone number.';
} elseif (!preg_match('/^[a-zA-Z0-9_]{3,}$/', $username)) {
$error_message = 'Username must be at least 3 characters and contain only letters, numbers, and underscore.';
} else {
try {
// Check if username already exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
$error_message = 'Username already exists.';
} else {
// Create new user
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (full_name, phone, username, password, role) VALUES (?, ?, ?, ?, ?)");
if ($stmt->execute([$full_name, $phone, $username, $hashed_password, $role])) {
$_SESSION['success_message'] = 'User created successfully.';
header("Location: users.php");
exit();
} else {
$error_message = 'Failed to create user.';
}
}
} catch (PDOException $e) {
$error_message = 'Database error: ' . $e->getMessage();
}
}
break;
case 'toggle_status':
$user_id = $_POST['user_id'] ?? 0;
$new_status = $_POST['status'] ?? 'active';
try {
$stmt = $pdo->prepare("UPDATE users SET status = ? WHERE id = ? AND id != ?");
if ($stmt->execute([$new_status, $user_id, $_SESSION['user_id']])) {
$_SESSION['success_message'] = 'User status updated successfully.';
} else {
$_SESSION['error_message'] = 'Failed to update user status.';
}
} catch (PDOException $e) {
$_SESSION['error_message'] = 'Database error.';
}
header("Location: users.php");
exit();
break;
}
}
}
// Fetch all users
try {
$stmt = $pdo->prepare("SELECT id, full_name, phone, username, role, status, created_at FROM users ORDER BY created_at DESC");
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$users = [];
$error_message = 'Failed to fetch users.';
}
include 'includes/header.php';
?>
No Users Found
Create your first user using the form above.
| Name |
Username |
Phone |
Role |
Status |
Created |
Actions |
|
|
|
|
|
|
Current User
|
-------------------- END OF FILE --------------------
### FILE 15: config/database.php
- Type: PHP
- Size: 967 B
- Path: config
- Name: database.php
------------------------------------------------------------
conn = new PDO(
"mysql:host=" . $this->host . ";dbname=" . $this->db_name . ";charset=utf8mb4",
$this->username,
$this->password,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
} catch(PDOException $exception) {
die("Connection error: " . $exception->getMessage());
}
return $this->conn;
}
}
// Create database instance
$database = new Database();
$pdo = $database->getConnection();
?>
-------------------- END OF FILE --------------------
### FILE 16: css/style.css
- Type: CSS
- Size: 10.79 KB
- Path: css
- Name: style.css
------------------------------------------------------------
/* CSS Variables for Colors */
:root {
--primary-yellow: #FFD700;
--dark-navy: #1a237e;
--light-navy: #3949ab;
--white: #ffffff;
--light-gray: #f5f5f5;
--dark-gray: #666666;
--success: #4caf50;
--error: #f44336;
--warning: #ff9800;
--shadow: rgba(26, 35, 126, 0.1);
}
/* Utility Classes */
.text-center { text-align: center; }
.text-right { text-align: right; }
.mb-1 { margin-bottom: 0.5rem; }
.mb-2 { margin-bottom: 1rem; }
.mb-3 { margin-bottom: 1.5rem; }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }
.d-none { display: none; }
.d-block { display: block; }
.d-flex { display: flex; }
.justify-between { justify-content: space-between; }
.align-center { align-items: center; }
.gap-1 { gap: 0.5rem; }
.gap-2 { gap: 1rem; }
/* Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: var(--dark-navy);
background-color: var(--light-gray);
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Header Styles */
.header {
background: linear-gradient(135deg, var(--dark-navy) 0%, var(--light-navy) 100%);
color: var(--white);
padding: 1rem;
box-shadow: 0 2px 10px var(--shadow);
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 1200px;
margin: 0 auto;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-yellow);
text-decoration: none;
}
.user-info {
display: flex;
align-items: center;
gap: 1rem;
}
.user-name {
font-weight: 500;
font-size: 0.9rem;
}
.logout-btn {
background: var(--primary-yellow);
color: var(--dark-navy);
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
font-size: 0.8rem;
}
.logout-btn:hover {
background: #ffed4e;
transform: translateY(-1px);
}
/* Navigation */
.nav-container {
background: var(--white);
padding: 1rem;
box-shadow: 0 2px 5px var(--shadow);
margin-bottom: 1rem;
}
.nav-menu {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
max-width: 1200px;
margin: 0 auto;
}
.nav-item {
display: block;
text-align: center;
padding: 0.8rem 0.5rem;
background: var(--light-gray);
color: var(--dark-navy);
text-decoration: none;
border-radius: 8px;
font-weight: 500;
font-size: 0.85rem;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.nav-item:hover,
.nav-item.active {
background: var(--primary-yellow);
color: var(--dark-navy);
border-color: var(--dark-navy);
transform: translateY(-2px);
}
/* Main Content */
.main-content {
flex: 1;
max-width: 1200px;
margin: 0 auto;
width: 100%;
padding: 0 1rem;
}
.page-header {
margin-bottom: 2rem;
text-align: center;
}
.page-title {
font-size: 2rem;
color: var(--dark-navy);
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--dark-gray);
font-size: 1rem;
}
/* Cards */
.card {
background: var(--white);
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 4px 15px var(--shadow);
border: 1px solid #e0e0e0;
}
.card-header {
border-bottom: 2px solid var(--primary-yellow);
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
.card-title {
color: var(--dark-navy);
font-size: 1.2rem;
font-weight: 600;
}
/* Forms */
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--dark-navy);
font-size: 0.9rem;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 0.8rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: all 0.3s ease;
background: var(--white);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary-yellow);
box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
/* Buttons */
.btn {
display: inline-block;
padding: 0.8rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
font-size: 0.9rem;
text-align: center;
min-width: 100px;
}
.btn-primary {
background: var(--dark-navy);
color: var(--white);
}
.btn-primary:hover {
background: var(--light-navy);
transform: translateY(-1px);
}
.btn-secondary {
background: var(--primary-yellow);
color: var(--dark-navy);
}
.btn-secondary:hover {
background: #ffed4e;
transform: translateY(-1px);
}
.btn-success {
background: var(--success);
color: var(--white);
}
.btn-danger {
background: var(--error);
color: var(--white);
}
.btn-full {
width: 100%;
}
/* Tables */
.table-container {
overflow-x: auto;
margin-bottom: 1rem;
}
.table {
width: 100%;
border-collapse: collapse;
background: var(--white);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px var(--shadow);
}
.table th,
.table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.table th {
background: var(--dark-navy);
color: var(--white);
font-weight: 600;
font-size: 0.9rem;
}
.table tr:hover {
background: #f9f9f9;
}
/* Alerts */
.alert {
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
font-weight: 500;
border-left: 4px solid;
}
.alert-success {
background: #e8f5e8;
color: #2d5932;
border-left-color: var(--success);
}
.alert-error {
background: #fdeaea;
color: #721c24;
border-left-color: var(--error);
}
.alert-warning {
background: #fff8e1;
color: #663c00;
border-left-color: var(--warning);
}
/* Loading Spinner */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid var(--primary-yellow);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Footer */
.footer {
background: var(--dark-navy);
color: var(--white);
text-align: center;
padding: 1rem;
margin-top: auto;
}
/* Login Page Specific */
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--dark-navy) 0%, var(--light-navy) 100%);
padding: 1rem;
}
.login-card {
background: var(--white);
padding: 2rem;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 100%;
max-width: 400px;
}
.login-logo {
text-align: center;
margin-bottom: 2rem;
}
.login-logo h1 {
color: var(--dark-navy);
font-size: 2rem;
margin-bottom: 0.5rem;
}
.login-logo p {
color: var(--dark-gray);
font-size: 0.9rem;
}
/* Dashboard Grid */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: linear-gradient(135deg, var(--primary-yellow) 0%, #ffed4e 100%);
color: var(--dark-navy);
padding: 1.5rem;
border-radius: 15px;
text-align: center;
box-shadow: 0 5px 15px rgba(255, 215, 0, 0.3);
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
}
.stat-number {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.stat-label {
font-weight: 600;
opacity: 0.8;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--dark-gray);
}
.empty-state h3 {
margin-bottom: 1rem;
color: var(--dark-navy);
}
/* Mobile Responsive */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 0.5rem;
}
.user-info {
justify-content: center;
}
.nav-menu {
grid-template-columns: repeat(2, 1fr);
}
.page-title {
font-size: 1.5rem;
}
.card {
padding: 1rem;
}
.table th,
.table td {
padding: 0.5rem;
font-size: 0.8rem;
}
.btn {
padding: 0.7rem 1rem;
font-size: 0.8rem;
}
}
@media (max-width: 480px) {
.nav-menu {
grid-template-columns: 1fr;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
}
/* Sales Line Items Styling */
.line-item-form {
background: var(--light-gray);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.line-items-table {
margin-top: 1rem;
}
.line-items-table .table tfoot td {
background: linear-gradient(135deg, var(--primary-yellow) 0%, #ffed4e 100%);
color: var(--dark-navy);
font-weight: 700;
border-top: 3px solid var(--dark-navy);
}
.bill-section-divider {
border-bottom: 2px solid var(--light-gray);
padding-bottom: 1rem;
margin-bottom: 2rem;
}
.payment-section-divider {
border-top: 2px solid var(--light-gray);
padding-top: 1rem;
margin-top: 2rem;
}
/* Editable line total inputs */
.line-total-input {
width: 100px;
display: inline-block;
padding: 0.4rem;
font-size: 0.9rem;
border: 2px solid var(--primary-yellow);
border-radius: 4px;
font-weight: bold;
}
.line-total-input:focus {
outline: none;
border-color: var(--dark-navy);
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
}
/* Sales history improvements */
.sales-item-details {
font-size: 0.85rem;
line-height: 1.3;
}
.sales-item-details > div {
margin-bottom: 0.25rem;
padding: 0.25rem 0;
border-bottom: 1px dotted #e0e0e0;
}
.sales-item-details > div:last-child {
border-bottom: none;
margin-bottom: 0;
}
/* Button group for actions */
.btn-group {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
@media (max-width: 768px) {
.btn-group {
flex-direction: column;
}
.line-total-input {
width: 80px;
font-size: 0.8rem;
}
.sales-item-details {
font-size: 0.8rem;
}
}
.text-right { text-align: right; }
.mb-1 { margin-bottom: 0.5rem; }
.mb-2 { margin-bottom: 1rem; }
.mb-3 { margin-bottom: 1.5rem; }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }
.d-none { display: none; }
.d-block { display: block; }
.d-flex { display: flex; }
.justify-between { justify-content: space-between; }
.align-center { align-items: center; }
.gap-1 { gap: 0.5rem; }
.gap-2 { gap: 1rem; }
-------------------- END OF FILE --------------------
### FILE 17: includes/footer.php
- Type: PHP
- Size: 230 B
- Path: includes
- Name: footer.php
------------------------------------------------------------