AMS_Backend/app/Controllers/Backend/CustomerController.php
2025-11-06 13:41:06 +08:00

1387 lines
47 KiB
PHP

<?php
namespace App\Controllers\Backend;
use App\Models\Customer;
use App\Models\StudentCard;
use App\Models\CheckinStreak;
use App\Models\CustomerPoint;
use App\Models\CustomerWallet;
use App\Models\CustomerAddress;
use App\Models\LogCustomerCheckin;
use App\Models\SettingCheckinRule;
use App\Models\SettingCustomerType;
use App\Models\SettingMembershipTier;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
helper('image');
class CustomerController extends ResourceController
{
private $customerModel;
private $customerTypeModel;
private $membershipTierModel;
private $customerAddressModel;
private $customerWalletModel;
private $customerPointModel;
private $checkinStreakModel;
private $logCheckinModel;
private $checkinRuleModel;
private $studentCardModel;
public function __construct()
{
$this->customerModel = new Customer();
$this->customerTypeModel = new SettingCustomerType();
$this->membershipTierModel = new SettingMembershipTier();
$this->customerAddressModel = new CustomerAddress();
$this->customerWalletModel = new CustomerWallet();
$this->customerPointModel = new CustomerPoint();
$this->checkinStreakModel = new CheckinStreak();
$this->logCheckinModel = new LogCustomerCheckin();
$this->checkinRuleModel = new SettingCheckinRule();
$this->studentCardModel = new StudentCard();
}
public function create()
{
helper(['form']);
$validationRules = [
'customer_type' => 'required',
'birthday' => 'required|valid_date[Y-m-d]',
'phone' => 'required|is_unique[customers.phone]',
'email' => 'required|valid_email|is_unique[customers.email]',
'password_hash' => 'required|min_length[8]',
'name' => 'required',
];
if (!$this->validate($validationRules)) {
return $this->failValidationErrors($this->validator->getErrors());
}
$customerTypeName = $this->request->getVar('customer_type');
$customerType = $this->customerTypeModel->where('name', $customerTypeName)->first();
if (!$customerType) {
$response = [
'status' => 'error',
'message' => "Customer type '{$customerTypeName}' not found.",
'data' => null
];
return $this->respond($response, 404);
}
$tierName = $this->request->getVar('customer_tier');
$tier = $this->membershipTierModel->where('name', $tierName)->first();
if (!$tier) {
$response = [
'status' => 'error',
'message' => "Membership tier '{$tierName}' not found.",
'data' => null
];
return $this->respond($response, 404);
}
// Profile picture
$profileImage = $this->request->getFile('profile_picture');
$imagePath = null;
$compressedPath = null;
if ($profileImage && $profileImage->isValid() && !$profileImage->hasMoved()) {
$originalPath = $_SERVER['DOCUMENT_ROOT'] . '/backend/uploads/customers';
$compressedDir = $_SERVER['DOCUMENT_ROOT'] . '/backend/uploads/customers_compressed';
try {
$result = save_image_with_compression($profileImage, $originalPath, $compressedDir, 900, 80);
// You can return relative paths if you want to store them in the DB
$imagePath = str_replace($_SERVER['DOCUMENT_ROOT'].'/', '', $result['original']);
$compressedPath = str_replace($_SERVER['DOCUMENT_ROOT'].'/', '', $result['compressed']);
} catch (\Exception $e) {
$response = [
'status' => 'error',
'message' => 'Profile picture upload failed: ' . $e->getMessage(),
'data' => null
];
return $this->respond($response, 500);
}
}
$data = [
'customer_type' => $customerTypeName,
'customer_tier_id' => $tier['id'],
'phone' => $this->request->getVar('phone'),
'email' => $this->request->getVar('email'),
'name' => $this->request->getVar('name'),
'password_hash' => md5($this->request->getVar('password')), // Fix match
'birthday' => $this->request->getVar('birthday'),
'customer_referral_id' => $this->request->getVar('customer_referral_id'),
'customer_wallet' => 0,
'customer_point' => 0,
'status' => 'active',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
if ($imagePath) {
$data['profile_picture'] = $imagePath;
}
if ($this->customerModel->insert($data)) {
$insertedId = $this->customerModel->getInsertID();
// Format the ID to be used as referralCode (e.g., 00001)
$referralCode = str_pad($insertedId, 6, '0', STR_PAD_LEFT);
// Update the customer record with the referralCode
$this->customerModel->update($insertedId, ['customer_referral_code' => $referralCode]);
unset($data['password_hash']);
$data['customer_referral_code'] = $referralCode;
$response = [
'status' => 'success',
'message' => 'Customer created successfully.',
'data' => [
'customer_id' => $insertedId,
'referral_code' => $referralCode,
'customer' => $data
]
];
return $this->respond($response, 200);
} else {
$response = [
'status' => 'error',
'message' => 'Failed to create customer.',
'data' => null
];
return $this->respond($response, 500);
}
}
public function index()
{
$customers = $this->customerModel
->select('
customers.id,
customers.name,
customers.email,
customers.phone,
customers.customer_type,
customers.birthday,
customers.profile_picture,
customers.status,
customers.customer_referral_id,
customers.customer_referral_code,
customers.customer_wallet,
customers.customer_point,
setting_membership_tiers.name AS customer_tier,
customers.created_at,
')
->join('setting_membership_tiers', 'setting_membership_tiers.id = customers.customer_tier_id', 'left')
->findAll();
$response = [
'status' => 'success',
'message' => 'Customer list retrieved successfully.',
'data' => $customers
];
return $this->respond($response, 200);
}
public function show($id = null)
{
if ($id === null) {
$response = [
'status' => 'error',
'message' => 'Customer ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
// First get customer basic info
$customer = $this->customerModel
->select('
customers.id,
customers.name,
customers.email,
customers.phone,
customers.customer_type,
customers.birthday,
customers.profile_picture,
customers.status,
customers.customer_referral_id,
customers.customer_referral_code,
customers.customer_wallet,
customers.customer_point,
setting_membership_tiers.name AS customer_tier,
customers.created_at
')
->join('setting_membership_tiers', 'setting_membership_tiers.id = customers.customer_tier_id', 'left')
->where('customers.id', $id)
->first();
if (!$customer) {
$response = [
'status' => 'error',
'message' => "Customer with ID {$id} not found.",
'data' => null
];
return $this->respond($response, 404);
}
// Then get vouchers separately
$vouchers = $this->customerModel
->db // Assuming you have access to the database connection
->table('customer_voucher_list')
->select('voucher_code, voucher_expiry_date, voucher_status')
->where('customer_id', $id)
->get()
->getResultArray();
// Build full URL for profile picture if it exists
if (!empty($customer['profile_picture'])) {
$baseUrl = 'https://icom.ipsgroup.com.my';
$customer['profile_picture_url'] = $baseUrl . '/backend/' . $customer['profile_picture'];
} else {
$customer['profile_picture_url'] = null;
}
// Add vouchers to customer data
$customer['vouchers'] = $vouchers;
$response = [
'status' => 'success',
'message' => 'Customer retrieved successfully.',
'data' => $customer
];
return $this->respond($response, 200);
}
public function update($id = null)
{
helper(['form']);
$customer = $this->customerModel->find($id);
if (!$customer) {
$response = [
'status' => 'error',
'message' => "Customer with ID {$id} not found.",
'data' => null
];
return $this->respond($response, 404);
}
$validationRules = [
'customer_type' => 'required',
'customer_tier' => 'required',
'birthday' => 'required|valid_date[Y-m-d]',
// 'phone' => "required|is_unique[customers.phone,id,{$id}]",
// 'email' => "required|valid_email|is_unique[customers.email,id,{$id}]",
'name' => 'required',
'password' => 'permit_empty|min_length[6]'
];
// Only add phone/email uniqueness check if they are being changed
if ($this->request->getVar('phone') !== $customer['phone']) {
$validationRules['phone'] = "required|is_unique[customers.phone,id,{$id}]";
} else {
$validationRules['phone'] = "required";
}
if ($this->request->getVar('email') !== $customer['email']) {
$validationRules['email'] = "required|valid_email|is_unique[customers.email,id,{$id}]";
} else {
$validationRules['email'] = "required|valid_email";
}
if (!$this->validate($validationRules)) {
$response =[
'status' => 'error',
'message' => 'Validation failed.',
'data' => $this->validator->getErrors()
];
return $this->respond($response, 422);
}
// Get raw input values
$customerTypeName = $this->request->getVar('customer_type');
$tierName = $this->request->getVar('customer_tier');
// Validate customer tier exists
$tier = $this->membershipTierModel->where('name', $tierName)->first();
if (!$tier) {
return $this->failNotFound("Membership tier '{$tierName}' not found.");
}
$data = [
'customer_type' => $customerTypeName, // Store name instead of ID
'customer_tier_id' => $tier['id'], // Store tier ID
'phone' => $this->request->getVar('phone'),
'email' => $this->request->getVar('email'),
'name' => $this->request->getVar('name'),
'birthday' => $this->request->getVar('birthday'),
'updated_at' => date('Y-m-d H:i:s'),
];
// Optional password update
$password = $this->request->getVar('password');
if (!empty($password)) {
$data['password_hash'] = md5($password);
}
// Handle profile picture update
$profileImage = $this->request->getFile('profile_picture');
if ($profileImage && $profileImage->isValid() && !$profileImage->hasMoved()) {
$uploadPath = $_SERVER['DOCUMENT_ROOT'] . '/backend/uploads/customers/';
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0755, true);
}
$imageName = time() . '_' . uniqid() . '.' . $profileImage->getClientExtension();
try {
$profileImage->move($uploadPath, $imageName);
// Delete old image if exists
if (!empty($customer['profile_picture'])) {
$oldImagePath = $_SERVER['DOCUMENT_ROOT'] . '/backend/' . $customer['profile_picture'];
if (file_exists($oldImagePath)) {
unlink($oldImagePath);
}
}
$baseUrl = 'https://icom.ipsgroup.com.my'; // or manually: 'https://icom.ipsgroup.com.my'
if (!empty($imageName)) {
$data['profile_picture'] = "uploads/customers/{$imageName}";
$data['profile_picture_url'] = $baseUrl . "/backend/uploads/customers/{$imageName}";
}
} catch (\Exception $e) {
log_message('error', 'Profile picture update failed: ' . $e->getMessage());
$response = [
'status' => 'error',
'message' => 'Profile picture update failed.',
'data' => null
];
return $this->respond($response, 500);
}
}
if ($this->customerModel->update($id, $data)) {
unset($data['password_hash']);
$response = [
'status' => 'success',
'message' => 'Customer updated successfully.',
'data' => $data
];
return $this->respond($response,200);
} else {
$response = [
'status' => 'error',
'message' => 'Failed to update customer.',
'data' => null
];
return $this->respond($response, 500);
}
}
public function delete($id = null)
{
if (!$id) {
$response = [
'status' => 'error',
'message' => 'Customer ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
$customer = $this->customerModel->find($id);
if (!$customer) {
$response = [
'status' => 'error',
'message' => "Customer with ID {$id} not found.",
'data' => null
];
return $this->respond($response, 404);
}
// Soft delete (if `useSoftDeletes = true` is enabled in your model)
if ($this->customerModel->delete($id)) {
$response = [
'status' => 'success',
'message' => "Customer with ID {$id} deleted successfully.",
'data' => null
];
return $this->respond($response, 200);
} else {
$response = [
'status' => 'error',
'message' => "Failed to delete customer.",
'data' => null
];
return $this->respond($response, 500);
}
}
//customer address
public function createCustomerAddress($customerId = null)
{
helper(['form']);
if (!$customerId) {
$response = [
'status' => 'error',
'message' => 'Customer ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
// Check if customer exists, is active, and not soft-deleted
$customer = $this->customerModel
->where('id', $customerId)
->where('status', 'active')
->where('deleted_at', null)
->first();
if (!$customer) {
$response = [
'status' => 'error',
'message' => "Customer with ID {$customerId} not found or not active.",
'data' => null
];
return $this->respond($response, 404);
}
// Load the model
$customerAddressModel = new CustomerAddress();
// Set all addresses for this customer to is_default = 0
$customerAddressModel
->where('customer_id', $customerId)
->set(['is_default' => 0])
->update();
$data = [
'customer_id' => $customerId,
'name' => $this->request->getVar('name'),
'phone' => $this->request->getVar('phone'),
'address' => $this->request->getVar('address'),
'unit' => $this->request->getVar('unit'),
'note' => $this->request->getVar('note'),
'is_default' => 1, // Always true for the newly added
'created_at' => date('Y-m-d H:i:s'),
];
if ($customerAddressModel->insert($data)) {
$response = [
'status' => 'success',
'message' => 'Customer address created successfully.',
'data' => $data
];
return $this->respond($response, 201);
} else {
$response =[
'status' => 'error',
'message' => 'Failed to create customer address.',
'data' => null
];
return $this->respond($response, 500);
}
}
public function customerAddressIndex()
{
$addresses = $this->customerAddressModel->findAll();
$response = [
'status' => 'success',
'message' => 'Customer addresses retrieved successfully.',
'data' => $addresses
];
return $this->respond($response, 200);
}
public function showCustomerAddress($id = null)
{
if ($id === null) {
$response = [
'status' => 'error',
'message' => 'Address ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
$address = $this->customerAddressModel->find($id);
if (!$address) {
$response = [
'status' => 'error',
'message' => "Customer address with ID {$id} not found.",
'data' => null
];
return $this->respond($response, 404);
}
$response = [
'status' => 'sucess',
'message' => 'Customer address retrieved successfully.',
'data' => $address
];
return $this->respond($response, 200);
}
public function showAddressesByCustomer($customerId = null)
{
if ($customerId === null) {
$response = [
'status' => 'error',
'message' => 'Customer ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
$addresses = $this->customerAddressModel->where('customer_id', $customerId)->findAll();
if (empty($addresses)) {
$response = [
'status' => 'error',
'message' => "No addresses found for customer ID {$customerId}.",
'data' => null
];
return $this->respond($response, 404);
}
$response = [
'status' => 'sucess',
'message' => 'Customer addresses retrieved successfully.',
'data' => $addresses
];
return $this->respond($response, 200);
}
public function updateCustomerAddress($id = null)
{
helper(['form']);
if ($id === null) {
$response = [
'status' => 'error',
'message' => 'Address ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
$address = $this->customerAddressModel->find($id);
if (!$address) {
$response = [
'status' => 'error',
'message' => "Customer address with ID {$id} not found.",
'data' => null
];
return $this->respond($response, 404);
}
// Fetch customer and validate
$customerId = $address['customer_id'];
$customer = $this->customerModel
->where('id', $customerId)
->where('status', 'active')
->where('deleted_at', null)
->first();
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found or inactive.',
'data' => null
];
return $this->respond($response, 404);
}
$validationRules = [
'name' => 'required',
'phone' => 'required',
'address' => 'required',
'unit' => 'permit_empty',
'note' => 'permit_empty',
'is_default'=> 'required|in_list[0,1]'
];
if (!$this->validate($validationRules)) {
$response = [
'status' => 'error',
'message' => 'Validation failed.',
'data' => $this->validator->getErrors()
];
return $this->respond($response, 422);
}
$data = [
'name' => $this->request->getVar('name'),
'phone' => $this->request->getVar('phone'),
'address' => $this->request->getVar('address'),
'unit' => $this->request->getVar('unit'),
'note' => $this->request->getVar('note'),
'is_default' => $this->request->getVar('is_default'),
'updated_at' => date('Y-m-d H:i:s')
];
// Handle is_default logic
if ($data['is_default'] == 1) {
// Set all other addresses of this customer to 0
$this->customerAddressModel
->where('customer_id', $customerId)
->where('id !=', $id)
->set('is_default', 0)
->update();
}
if ($this->customerAddressModel->update($id, $data)) {
$response = [
'status' => 'success',
'message' => 'Customer address updated successfully.',
'data' => $data
];
return $this->respond($response, 200);
} else {
$response = [
'status' => 'error',
'message' => 'Failed to update customer address.',
'data' => null
];
return $this->respond($response, 500);
}
}
public function deleteCustomerAddress($id = null)
{
if (!$id) {
$response = [
'status' => 'error',
'message' => 'Customer address ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
$address = $this->customerAddressModel->find($id);
if (!$address) {
$response = [
'status' => 'error',
'message' => "Customer address with ID {$id} not found.",
'data' => null
];
return $this->respond($response, 404);
}
// Delete the address
if ($this->customerAddressModel->delete($id)) {
// If deleted address was default, assign default to another one
if ($address['is_default'] == 1) {
$anotherAddress = $this->customerAddressModel
->where('customer_id', $address['customer_id'])
->where('id !=', $id)
->orderBy('created_at', 'DESC')
->first();
if ($anotherAddress) {
$this->customerAddressModel->update($anotherAddress['id'], ['is_default' => 1]);
}
}
$response = [
'status' => 'success',
'message' => "Customer address with ID {$id} deleted successfully.",
'data' => null
];
return $this->respond($response, 200);
}
$response = [
'status' => 'error',
'message' => "Failed to delete customer address.",
'data' => null
];
return $this->respond($response, 500);
}
public function setDefaultCustomerAddress($id = null) // $id from URL
{
$customerId = $this->request->getVar('customer_id');
// Validate customer ID input
if (!$customerId) {
$response = [
'status' => 'error',
'message' => 'Customer ID is required.',
'data' => null
];
return $this->respond($response, 422);
}
$customer = $this->customerModel
->where('id', $customerId)
->where('status', 'active')
->where('deleted_at', null)
->first();
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found or inactive.',
'data' => null
];
return $this->respond($response, 404);
}
// 2. Validate address exists AND belongs to the customer
$address = $this->customerAddressModel
->where('id', $id)
->where('customer_id', $customerId) // <- this ensures correct linkage
->first();
if (!$address) {
$response = [
'status' => 'error',
'message' => 'Address not found or does not belong to the customer.',
'data' => null
];
return $this->respond($response, 404);
}
// 3. Set all customer's addresses to is_default = 0
$this->customerAddressModel
->where('customer_id', $customerId)
->set(['is_default' => 0])
->update();
// 4. Set selected address to is_default = 1
$this->customerAddressModel->update($id, ['is_default' => 1]);
$response = [
'status' => 'success',
'message' => "Address ID {$id} is now the default for customer ID {$customerId}.",
'data' => [
'address_id' => $id,
'customer_id' => $customerId
]
];
return $this->respond($response,200);
}
//Customer Wallet Part
public function createCustomerWallet()
{
$data = [
'customer_id' => $this->request->getVar('customer_id'),
'related_type' => $this->request->getVar('related_type'),
'related_id' => $this->request->getVar('related_id'),
'action' => $this->request->getVar('action'),
'in' => $this->request->getVar('in'),
'out' => $this->request->getVar('out'),
'remark' => $this->request->getVar('remark'),
];
if (empty($data['customer_id']) || empty($data['action'])) {
$response = [
'status' => 'error',
'message' => 'Customer ID and action are required.',
'data' => null
];
return $this->respond($response, 422);
}
// Normalize and validate action
$action = strtolower(trim($data['action']));
if (!in_array($action, ['in', 'out'])) {
$response = [
'status' => 'error',
'message' => 'The action must be either "in" or "out".',
'data' => null
];
return $this->respond($response, 422);
}
// Ensure only one of 'in' or 'out' is provided
$in = $action === 'in' ? floatval($data['in'] ?? 0) : 0;
$out = $action === 'out' ? floatval($data['out'] ?? 0) : 0;
if (($in <= 0 && $out <= 0) || ($in > 0 && $out > 0)) {
$response = [
'status' => 'error',
'message' => 'Provide a valid positive amount only for the action specified.',
'data' => null
];
return $this->respond($response, 422);
}
// Check customer existence and active status
$customer = $this->customerModel
->where('id', $data['customer_id'])
->where('status', 'active')
->where('deleted_at', null)
->first();
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found or inactive.',
'data' => null
];
return $this->respond($response, 404);
} // Fixed: Added closing brace for the if statement
// Get current balance and calculate new balance
$currentBalance = floatval($customer['customer_wallet']);
$newBalance = $currentBalance + $in - $out;
// Prepare insert data
$walletData = [
'customer_id' => $data['customer_id'],
'related_id' => $data['related_id'] ?? null,
'related_type' => $data['related_type'] ?? null,
'action' => $action,
'current' => $currentBalance,
'in' => $in,
'out' => $out,
'balance' => $newBalance,
'remark' => $data['remark'] ?? null,
'created_at' => date('Y-m-d H:i:s'),
];
// Save to customer_wallets and Update customer balance
$this->customerWalletModel->insert($walletData);
$this->customerModel->update($data['customer_id'], ['customer_wallet' => $newBalance]);
$response = [
'status' => 'success',
'message' => 'Customer wallet transaction created successfully.',
'data' => $walletData,
];
return $this->respond($response, 201);
}
public function getCustomerWalletHistory($customerId)
{
// Check if customer exists
$customer = $this->customerModel->find($customerId);
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found.',
'data' => null
];
return $this->respond($response, 404);
}
$dateFrom = $this->request->getGet('date_from'); // expected format: Y-m-d
$dateTo = $this->request->getGet('date_to');
// Base query
$builder = $this->customerWalletModel
->where('customer_id', $customerId)
->orderBy('created_at', 'desc');
// Optional filters
if ($dateFrom) {
$builder->where('DATE(created_at) >=', $dateFrom);
}
if ($dateTo) {
$builder->where('DATE(created_at) <=', $dateTo);
}
// All transactions
$transactions = $builder->findAll();
// If no transactions found
if (empty($transactions)) {
$response = [
'status' => 'success',
'message' => 'No wallet history found for this customer.',
'data' => [
'all' => [],
'credit' => []
]
];
return $this->respond($response,200);
}
// Credit-only transactions
$creditTransactions = array_filter($transactions, function ($t) {
return strtolower($t['action']) === 'in';
});
$response = [
'status' => 'success',
'message' => 'Customer wallet history retrieved successfully.',
'data' => [
'all' => $transactions,
'credit' => array_values($creditTransactions)
]
];
return $this->respond($response,200);
}
// Customer Point Part
public function createCustomerPoint()
{
$data = [
'customer_id' => $this->request->getVar('customer_id'),
'related_type' => $this->request->getVar('related_type'),
'related_id' => $this->request->getVar('related_id'),
'action' => $this->request->getVar('action'),
'in' => $this->request->getVar('in'),
'out' => $this->request->getVar('out'),
'remark' => $this->request->getVar('remark'),
];
// Check required fields
if (!isset($data['customer_id'], $data['action'])) {
$response = [
'status' => 'error',
'message' => 'customer_id and action are required.',
'data' => null
];
return $this->respond($response, 422);
}
// Normalize and validate action
$action = strtolower(trim($data['action']));
if (!in_array($action, ['increase', 'decrease'])) {
$response = [
'status' => 'error',
'message' => 'The action must be either "increase" or "decrease".',
'data' => null
];
return $this->respond($response, 422);
}
// Use 'in' and 'out' fields, based on action type
$in = $action === 'increase' ? floatval($data['in'] ?? 0) : 0;
$out = $action === 'decrease' ? floatval($data['out'] ?? 0) : 0;
// Validation to ensure only one of them is provided and positive
if (($in <= 0 && $out <= 0) || ($in > 0 && $out > 0)) {
$response = [
'status' => 'error',
'message' => 'Provide a valid positive amount only for the action specified.',
'data' => null
];
return $this->respond($response, 422);
}
// Check if customer exists and is active
$customer = $this->customerModel
->where('id', $data['customer_id'])
->where('status', 'active')
->where('deleted_at', null)
->first();
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found or inactive.',
'data' => null
];
return $this->respond($response, 404);
}
// Current balance and new balance calculation
$currentPoint = floatval($customer['customer_point']);
$newPoint = $currentPoint + $in - $out;
// Prepare the data to insert
$pointData = [
'customer_id' => $data['customer_id'],
'related_id' => $data['related_id'] ?? null,
'related_type' => $data['related_type'] ?? null,
'action' => $action,
'current' => $currentPoint,
'in' => $in,
'out' => $out,
'balance' => $newPoint,
'remark' => $data['remark'] ?? null,
'created_at' => date('Y-m-d H:i:s'),
];
// Save to customer_points table
$this->customerPointModel->insert($pointData);
// Update customer's overall point balance
$this->customerModel->update($data['customer_id'], ['customer_point' => $newPoint]);
$response = [
'status' => 'success',
'message' => 'Customer point transaction created successfully.',
'data' => $pointData
];
return $this->respond($response, 201);
}
public function getCustomerPointHistory($customerId)
{
// Check if customer exists
$customer = $this->customerModel->find($customerId);
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found.',
'data' => null
];
return $this->respond($response, 404);
}
$dateFrom = $this->request->getGet('date_from'); // optional
$dateTo = $this->request->getGet('date_to'); // optional
// Build query
$builder = $this->customerPointModel
->where('customer_id', $customerId)
->orderBy('created_at', 'desc');
if ($dateFrom) {
$builder->where('DATE(created_at) >=', $dateFrom);
}
if ($dateTo) {
$builder->where('DATE(created_at) <=', $dateTo);
}
$transactions = $builder->findAll();
$response = [
'status' => 'success',
'message' => empty($transactions)
? 'No data available in the table.'
: 'Customer point history retrieved successfully.',
'data' => $transactions
];
return $this->respond($response, 200);
}
public function checkin($customerId)
{
$today = date('Y-m-d');
$yesterday = date('Y-m-d', strtotime('-1 day'));
//hardcode the date for testing purpose.
// $today = '2025-07-09';
// $yesterday = '2025-07-08';
// Validate input
if (!$customerId) {
$response = [
'status' => 'error',
'message' => 'Customer ID is required.',
'data' => null
];
return $this->respond($response, 400);
}
//Check if customer exists
$customer = $this->customerModel->find($customerId);
if (!$customer) {
$response = [
'status' => 'error',
'message' => 'Customer not found.',
'data' => null
];
return $this->respond($response, 404);
}
//Prevent multiple check-ins on same day
$alreadyChecked = $this->logCheckinModel
->where('customer_id', $customerId)
->where('checkin_date', $today)
->first();
if ($alreadyChecked) {
$response = [
'status' => 'error',
'message' => 'Already checked in today.',
'data' => null
];
return $this->respond($response, 409);
}
//Get or initialize streak
$streak = $this->checkinStreakModel->where('customer_id', $customerId)->first();
if ($streak) {
if ($streak['last_checkin_date'] === $yesterday) {
$currentStreak = $streak['current_streak'] + 1;
} else {
$currentStreak = 1; // missed a day, reset streak
}
$totalCheckin = $streak['total_checkin'] + 1;
$this->checkinStreakModel->update($streak['id'], [
'current_streak' => $currentStreak,
'last_checkin_date' => $today,
'total_checkin' => $totalCheckin,
]);
} else {
// first time ever
$currentStreak = 1;
$totalCheckin = 1;
$this->checkinStreakModel->insert([
'customer_id' => $customerId,
'current_streak' => $currentStreak,
'last_checkin_date' => $today,
'total_checkin' => $totalCheckin,
]);
}
//Log the check-in
$this->logCheckinModel->insert([
'customer_id' => $customerId,
'checkin_date' => $today,
'streak_count' => $currentStreak,
]);
//Check for reward rule
$rewardGiven = false;
$rewardPoint = 0;
$rule = $this->checkinRuleModel
->where('checkin_count', $currentStreak)
->first();
if ($rule) {
$alreadyRewarded = $this->customerPointModel
->where('customer_id', $customerId)
->where('remark', "Reward for {$currentStreak}-day check-in streak on {$today}.")
->first();
if (!$alreadyRewarded) {
$latestPoint = $this->customerPointModel
->where('customer_id', $customerId)
->orderBy('id', 'DESC')
->first();
$previousBalance = $latestPoint ? $latestPoint['balance'] : 0;
$rewardPoint = (float) $rule['reward_point'];
$newBalance = $previousBalance + $rewardPoint;
$this->customerPointModel->insert([
'customer_id' => $customerId,
'related_id' => '2',
'related_type' => 'checkin_reward',
'action' => 'increase',
'current' => $previousBalance,
'in' => $rewardPoint,
'out' => 0,
'balance' => $newBalance,
'remark' => "Reward for {$currentStreak}-day check-in streak on {$today}.",
]);
$this->customerModel->update($customerId, ['customer_point' => $newBalance]);
$rewardGiven = true;
}
}
$updatedStreak = $this->checkinStreakModel
->where('customer_id', $customerId)
->first();
$latestPoint = $this->customerPointModel
->where('customer_id', $customerId)
->orderBy('id', 'DESC')
->first();
$data = [
'reward_given' => $rewardGiven,
'reward_point' => $rewardPoint,
'customer_point' => $latestPoint['balance'],
'current_streak' => $updatedStreak['current_streak'],
];
$response = [
'status' => 'success',
'message' => 'Check-in successful!',
'data' => $data,
];
return $this->respond($response, 200);
}
private function handleStudentCardGraduation($studentCard)
{
if (!empty($studentCard['graduate_date']) && !empty($studentCard['customer_id'])) {
$today = date('Y-m-d');
if ($studentCard['graduate_date'] <= $today) {
$this->studentCardModel->delete($studentCard['id']);
$this->customerModel->update($studentCard['customer_id'], [
'customer_type' => 'Regular Customers',
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
}
public function studentCardIndex()
{
// Assuming your StudentCard model is loaded as $this->studentCardModel
$studentCards = $this->studentCardModel
->select('*') // Select all columns from student_cards
->findAll();
foreach ($studentCards as $studentCard) {
$this->handleStudentCardGraduation($studentCard);
}
$response = [
'status' => 'success',
'message' => 'Student card list retrieved successfully.',
'data' => $studentCards
];
return $this->respond($response, 200);
}
public function studentCardShow($student_card_id = null)
{
if ($student_card_id === null) {
return $this->respond([
'status' => 'error',
'message' => 'Student card ID is required.',
'data' => null
], 422);
}
$studentCard = $this->studentCardModel->find($student_card_id);
if (!$studentCard) {
$this->handleStudentCardGraduation($studentCard);
return $this->respond([
'status' => 'error',
'message' => "Student card with ID {$student_card_id} not found.",
'data' => null
], 404);
}
return $this->respond([
'status' => 'success',
'message' => 'Student card retrieved successfully.',
'data' => $studentCard
], 200);
}
public function studentCardUpdateStatus($student_card_id = null)
{
helper(['form']);
if ($student_card_id === null) {
return $this->respond([
'status' => 'error',
'message' => 'Student card ID is required.',
'data' => null
], 422);
}
// Validate status input
$status = $this->request->getVar('status');
$allowedStatuses = ['approved', 'rejected'];
if (!in_array($status, $allowedStatuses)) {
return $this->respond([
'status' => 'error',
'message' => 'Invalid status value. Allowed values are: approved, rejected.',
'data' => null
], 422);
}
// Check if the student card exists
$studentCard = $this->studentCardModel->find($student_card_id);
if (!$studentCard) {
return $this->respond([
'status' => 'error',
'message' => "Student card with ID {$student_card_id} not found.",
'data' => null
], 404);
}
$data = [
'status' => $status,
'updated_at' => date('Y-m-d H:i:s'),
];
if ($this->studentCardModel->update($student_card_id, $data)) {
// If approved, also update the customer_type in the Customer table
if ($status === 'approved' && !empty($studentCard['customer_id'])) {
//search membership tier with tier_name = 'Student'
$membershipTier = $this->membershipTierModel->where('name', 'Student')->first();
if(!$membershipTier) {
return $this->respond([
'status' => 'error',
'message' => 'Student membership tier not found.',
'data' => null
], 404);
}else{
$this->customerModel->update($studentCard['customer_id'], [
'customer_type' => 'Student',
'customer_tier_id' => $membershipTier['id'],
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
return $this->respond([
'status' => 'success',
'message' => "Student card status updated to '{$status}'.",
'data' => $data
], 200);
} else {
return $this->respond([
'status' => 'error',
'message' => 'Failed to update student card status.',
'data' => null
], 500);
}
}
}