initial project

This commit is contained in:
2025-11-06 13:41:06 +08:00
commit 1b08727cb2
209 changed files with 31602 additions and 0 deletions
View File
+14
View File
@@ -0,0 +1,14 @@
<?php
if (!function_exists('getColumnValues')) {
function getColumnValues($model, string $whereColumn, $whereValue, string $columnToExtract)
{
$data = $model->where($whereColumn, $whereValue)->findAll();
if($data){
return array_column($data, $columnToExtract);
}else{
return [];
}
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
if (!function_exists('send_api_request')) {
/**
* Send external API requests with cURL.
*
* @param string $method HTTP method: GET, POST, PUT, PATCH, DELETE
* @param string $url Full URL to request
* @param array $headers Optional headers (e.g., Authorization, Content-Type)
* @param mixed $body Optional request body (array for form, JSON string for raw)
* @param bool $as_json Decode response as JSON (default: true)
* @return mixed Decoded JSON, raw string, or false on error
*/
function send_api_request($method, $url, $headers = [], $body = null, $as_json = true) {
$curl = curl_init();
// Set HTTP method
$method = strtoupper($method);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
// Attach body if needed
if (in_array($method, ['POST', 'PUT', 'PATCH']) && $body !== null) {
if (is_array($body)) {
// Form-encoded
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($body));
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
} else {
// Assume JSON string
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
$headers[] = 'Content-Type: application/json';
}
}
// Set cURL options
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
]);
if (!empty($headers)) {
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
// Execute
$response = curl_exec($curl);
$error = curl_error($curl);
curl_close($curl);
if ($error) {
print_r($error);
exit;
log_message('error', 'cURL Error: ' . $error);
return false;
}
return $as_json ? json_decode($response, true) : $response;
}
}
if (!function_exists('json_encode_decode')) {
function json_encode_decode($type, $data) {
if ($type == 'encode'){
return json_encode( $data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE ) ;
}else{
return json_decode( $data, true ) ;
}
return null;
}
}
if(!function_exists('number_format_no_round')){
function number_format_no_round($number, $decimals = 2, $dec_point = '.', $thousands_sep = ',') {
$factor = pow(10, $decimals);
// Truncate instead of rounding
$truncated = floor($number * $factor) / $factor;
return number_format($truncated, $decimals, $dec_point, $thousands_sep);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
use Config\Services;
if (!function_exists('save_image_with_compression')) {
/**
* Save original image and a compressed (resized) version.
*
* @param \CodeIgniter\HTTP\Files\UploadedFile $uploadedFile
* @param string $originalPath Folder for original image
* @param string $compressedPath Folder for compressed image
* @param int $compressedWidth Target width for compressed image
* @param int $quality JPEG quality (0-100)
* @return array ['original' => ..., 'compressed' => ...]
*/
function save_image_with_compression($uploadedFile, $originalPath, $compressedPath, $compressedWidth = 900, $quality = 80)
{
// Make sure directories exist
if (!is_dir($originalPath)) mkdir($originalPath, 0755, true);
if (!is_dir($compressedPath)) mkdir($compressedPath, 0755, true);
// Save original image
$newName = $uploadedFile->getRandomName();
$uploadedFile->move($originalPath, $newName);
$originalImage = rtrim($originalPath, '/\\') . '/' . $newName;
// Resize and compress
$imageService = Services::image('gd'); // or 'imagick' if enabled
$imageService
->withFile($originalImage)
->resize($compressedWidth, 0, true) // keep aspect ratio
->save(rtrim($compressedPath, '/\\') . '/' . $newName, $quality);
return [
'original' => $originalImage,
'compressed' => rtrim($compressedPath, '/\\') . '/' . $newName,
];
}
}
+198
View File
@@ -0,0 +1,198 @@
<?php
use App\Models\MenuCategories;
use App\Models\MenuImages;
use App\Models\MenuItemCategories;
use App\Models\MenuItemOptionsGroups;
use App\Models\MenuItems;
use App\Models\MenuItemTags;
use App\Models\MenuItemVariations;
use App\Models\OptionsGroups;
use App\Models\RegularItemAvailability;
use App\Models\SeasonalItemAvailability;
use App\Models\Tags;
use App\Models\VariationOptionsGroups;
use App\Models\VariationTags;
if (!function_exists('getMenuImage')) {
function getMenuImage($id)
{
$menu_images = new MenuImages();
$images_data = $menu_images->where('menu_item_id', $id)
->where('deleted_at', NULL)
->findAll();
$data = [];
foreach ($images_data as $image) {
$data[] = [
'id' => $image['id'],
'image_url' => getImagePath('menu_images', $image['image_url']),
'order_index' => $image['order_index']
];
}
return $data;
}
}
if (!function_exists('getMenuCategory')) {
function getMenuCategory($id)
{
$menu_item_category = new MenuItemCategories();
$category = new MenuCategories();
$category_data = $menu_item_category->where('menu_item_id', $id)
->where('deleted_at', NULL)
->findAll();
$data = [];
foreach ($category_data as $menu_category) {
$category_detail = $category->withDeleted()->find($menu_category['category_id']);
if ($category_detail) {
$data[] = [
'id' => $menu_category['category_id'],
'title' => $category_detail['title']
];
}
}
return $data;
}
}
if (!function_exists('getImagePath')) {
function getImagePath($path, $name)
{
$full_path = '';
if ($name) {
$full_path = 'https://' . $_SERVER['SERVER_NAME'] . '/backend/uploads/' . $path . '/' . $name;
}
return $full_path;
}
}
if (!function_exists('getVariationRelation')) {
function getVariationRelation($menu_item_id)
{
$array = [];
$menu_varition = new MenuItemVariations();
$variation_tag = new VariationTags();
$variation_option_group = new VariationOptionsGroups();
$tagModel = new Tags();
$optionGroupModel = new OptionsGroups();
$menu_variation_data = $menu_varition
->where('menu_item_id', $menu_item_id)
->findAll();
foreach ($menu_variation_data as $menu_variation) {
$variation_id = $menu_variation['id'];
$variation_tags = $variation_tag
->where('variation_id', $variation_id)
->findAll();
$tags = [];
foreach ($variation_tags as $vt) {
$tag = $tagModel->find($vt['tag_id']);
if ($tag) {
$tags[] = $tag;
}
}
$variation_option_groups = $variation_option_group
->where('variation_id', $variation_id)
->findAll();
$option_groups = [];
foreach ($variation_option_groups as $vog) {
$group = $optionGroupModel->find($vog['option_group_id']);
if ($group) {
$group['order_index'] = $vog['order_index'];
$option_groups[] = $group;
}
}
// Sort $option_groups by 'order_index' ascending
usort($option_groups, function ($a, $b) {
return $a['order_index'] <=> $b['order_index'];
});
$array[] = [
'variation' => $menu_variation,
'tags' => $tags,
'option_groups' => $option_groups,
];
}
return $array;
}
}
if (!function_exists('getMenuTag')) {
function getMenuTag($id)
{
$menu_item_tag = new MenuItemTags();
$tags = new Tags();
$menu_item_tag_data = $menu_item_tag->where('menu_item_id', $id)
->where('deleted_at', NULL)
->findAll();
$data = [];
foreach ($menu_item_tag_data as $menu_tag) {
$tag = $tags->withDeleted()->find($menu_tag['tag_id']);
$data[] = [
'id' => $menu_tag['tag_id'],
'title' => $tag['title'],
'icon_url' => getImagePath('tags', $tag['icon_url'])
];
}
return $data;
}
}
if (!function_exists('getMenuOptionGroup')) {
function getMenuOptionGroup($id)
{
$menu_item_option_group = new MenuItemOptionsGroups();
$option_groups = new OptionsGroups();
$option_groups_data = $menu_item_option_group->where('menu_item_id', $id)
->where('deleted_at', NULL)
->orderBy('order_index', 'ASC')
->findAll();
$data = [];
foreach ($option_groups_data as $option_group) {
$option_group_detail = $option_groups->withDeleted()->find($option_group['option_group_id']);
$data[] = [
'id' => $option_group['option_group_id'],
'title' => $option_group_detail['title'],
];
}
return $data;
}
}
if (!function_exists('getAvailability')) {
function getAvailability($menu_item_id, $type)
{
$regular_item_availability = new RegularItemAvailability();
$seasonal_item_availability = new SeasonalItemAvailability();
$data = [];
if ($type == 'seasonal') {
$seasonal = $seasonal_item_availability->where('menu_item_id', $menu_item_id)->first();
$data[] = $seasonal;
} else {
$regular = $regular_item_availability->where('menu_item_id', $menu_item_id)->findAll();
$data[] = $regular;
}
return $data;
}
}
+338
View File
@@ -0,0 +1,338 @@
<?php
use App\Models\Orders;
use App\Models\Outlet;
use App\Models\Customer;
use App\Models\MenuItems;
use App\Models\OrderItems;
use App\Models\OrderTaxes;
use App\Libraries\Lalamove;
use App\Models\CustomerPoint;
use App\Models\CustomerVoucherList;
use App\Models\OrderPayments;
use App\Models\CustomerWallet;
use App\Models\OrderDeliveries;
use App\Models\OrderItemOptions;
use App\Libraries\Wato;
if (!function_exists('getOrderType')) {
function getOrderType($id)
{
$orders = new Orders();
$order = $orders
->select('order_type')
->where('id', $id)
->where('deleted_at', null)
->first();
if (!$order) {
return '';
}
return $order['order_type'] ?? '';
}
}
if (!function_exists('getFullOrderDetail')) {
function getFullOrderDetail($id)
{
$orders = new Orders();
$order_items = new OrderItems();
$order_item_options = new OrderItemOptions();
$order_payments = new OrderPayments();
$order_taxes = new OrderTaxes();
$order_detail = array();
$order = $orders->find($id);
if (!$order) {
return array();
}
$order_detail = $order;
$order_detail['items'] = $order_items->where('order_id', $order['id'])->findAll();
foreach ($order_detail['items'] as &$item) {
$item['options'] = $order_item_options
->where('order_item_id', $item['id'])
->findAll();
}
$order_detail['taxes'] = $order_taxes
->where('order_id', $order['id'])
->findAll();
$order_detail['payments'] = $order_payments
->where('order_id', $order['id'])
->orderBy('created_at', 'DESC')
->findAll();
// $order_detail['item'] =
return $order_detail;
}
}
if (!function_exists('completeOrder')) {
function completeOrder($order_id)
{
$orders = new Orders();
$customers = new Customer();
$order_payments = new OrderPayments();
$customer_wallet = new CustomerWallet();
$customer_point_history = new CustomerPoint();
//get order and customer data
$order = $orders->select('orders.*, customers.*, order_payments.*')
->join('customers', 'customers.id = orders.customer_id AND customers.deleted_at IS NULL')
->join('order_payments', 'order_payments.order_id = orders.id AND order_payments.deleted_at IS NULL')
->where('orders.id', $order_id)
->where('orders.deleted_at', null)
->first();
if (!$order) {
return false;
}
//get payment method
$order_so = $order['order_so'];
$payment_method = $order['payment_method'];
$customer_id = $order['customer_id'];
$grand_total = $order['grand_total'];
$outlet_id = $order['outlet_id'];
$order_type = $order['order_type'];
//get outlet data
$outlets = new Outlet();
$outlet = $outlets->where('id', $outlet_id)->where('deleted_at', null)->first();
//update payment status
$orders->update($order_id, ['payment_status' => 'paid']);
switch ($payment_method) {
case 'wallet':
$wallet_balance = $order['customer_wallet'] - $grand_total;
$customers->update($customer_id, ['customer_wallet' => $wallet_balance]);
$order_payments->update($order_id, ['status' => 'completed', 'paid_at' => date('Y-m-d H:i:s')]);
$customer_wallet_transaction = [
'customer_id' => $customer_id,
'related_id' => $order_id,
'related_type' => 'order',
'action' => 'out',
'current' => $order['customer_wallet'],
'in' => 0,
'out' => $grand_total,
'balance' => $wallet_balance,
'remark' => $grand_total . ' deducted from wallet for order ' . $order_so,
];
$customer_wallet->insert($customer_wallet_transaction);
break;
default:
//update order payments
$order_payments->update($order_id, ['status' => 'completed', 'paid_at' => date('Y-m-d H:i:s')]);
break;
}
//update point
$customer_point = $order['customer_point'] + floor($grand_total);
$customers->update($customer_id, ['customer_point' => $customer_point]);
$customer_point_transaction = [
'customer_id' => $customer_id,
'related_id' => $order_id,
'related_type' => 'order',
'action' => 'in',
'current' => $order['customer_point'],
'in' => floor($grand_total),
'out' => 0,
'balance' => $customer_point,
'remark' => floor($grand_total) . ' points earned from order ' . $order_so,
];
$customer_point_history->insert($customer_point_transaction);
//get customer point history cumulative
$customer_point_history_cumulative = $customer_point_history->where('customer_id', $customer_id)->where('deleted_at', null)->selectSum('in')->get()->getRow()->in ?? 0;
//get membership tier setting and check for automatic upgrade
$setting_membership_tier = new \App\Models\SettingMembershipTier();
$membership_tier_setting = $setting_membership_tier->where('deleted_at', null)->orderBy('min_points', 'ASC')->findAll();
// Get current customer membership tier
$current_customer = $customers->where('id', $customer_id)->first();
$current_membership_tier = $current_customer['customer_tier_id'] ?? 0;
$get_current_tier = $setting_membership_tier->where('id', $current_membership_tier)->first();
$current_tier_min_points = $get_current_tier['min_points'] ?? 0;
// echo($customer_point_history_cumulative);exit;
// Check if customer qualifies for higher membership tier
$new_membership_tier = $current_membership_tier;
foreach ($membership_tier_setting as $tier) {
if ($customer_point_history_cumulative >= $tier['min_points'] && $tier['min_points'] > $current_tier_min_points) {
// Customer qualifies for this tier
// if($tier['id'] > $current_membership_tier){
$new_membership_tier = $tier['id'];
log_message('info', 'Customer ' . $customer_id . ' upgraded from tier ' . $current_membership_tier . ' to tier ' . $new_membership_tier . ' (Points: ' . $customer_point_history_cumulative . ')');
// }
}
}
// Update customer membership tier if upgraded
if ($new_membership_tier != $current_membership_tier) {
$customers->update($customer_id, [
'customer_tier_id' => $new_membership_tier
]);
log_message('info', 'Customer ' . $customer_id . ' membership tier updated to: ' . $new_membership_tier);
}
if ($order_type == 'delivery') { //need to check date & time as well
// Check which delivery service was used based on quotation ID
if ($order['lalamove_quot_id'] != null) {
// Lalamove was used for quotation
$lalamove = new Lalamove();
$lalamove_response = $lalamove->submitOrder($order_id);
if (isset($lalamove_response['data']['orderId'])) {
$order_delivery = new OrderDeliveries();
$order_delivery->insert([
'order_id' => $order_id,
'provider_name' => 'Lalamove',
'status' => 'success',
'provider_order_id' => $lalamove_response['data']['orderId'],
'fee_amount' => $lalamove_response['data']['priceBreakdown']['total'],
'actual_fee_amount' => $lalamove_response['data']['priceBreakdown']['total'],
'transaction_id' => $lalamove_response['data']['quotationId'],
'tracking_link' => $lalamove_response['data']['shareLink'],
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
} else {
return false;
}
} else if ($order['grab_quot_id'] != null) {
// Grab was used for quotation
$grab = new \App\Libraries\Grab();
$grab_response = $grab->submitOrder($order_id);
if (isset($grab_response['deliveryID'])) {
$order_delivery = new OrderDeliveries();
$order_delivery->insert([
'order_id' => $order_id,
'provider_name' => 'Grab',
'status' => 'success',
'provider_order_id' => $grab_response['deliveryID'],
'fee_amount' => $order['delivery_fee'] ?? 0,
'actual_fee_amount' => $order['delivery_fee'] ?? 0,
'transaction_id' => $order['grab_quot_id'] ?? null,
'tracking_link' => $grab_response['trackingURL'] ?? null,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
//update order status
$orders->update($order_id, ['grab_quot_id' => $grab_response['deliveryID']]);
} else {
return false;
}
} else if ($order['delivery_fee'] > 0) {
//manual delivery fee
$order_delivery = new OrderDeliveries();
$order_delivery->insert([
'order_id' => $order_id,
'provider_name' => 'Manual',
'status' => 'success',
'fee_amount' => $order['delivery_fee'] ?? 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
// Get all items for this order
$order_items = new OrderItems();
$menu_item = new MenuItems();
$customer_voucher_lists = new CustomerVoucherList();
$membershipTier = null;
$hasMembershipItem = false;
$orderItems = $order_items->where('order_id', $order_id)->findAll();
// Loop through items in order — assume $orderItems is already in "latest first" order
foreach ($orderItems as $item) {
$menuItem = $menu_item->where('id', $item['menu_item_id'])->first();
if ($menuItem && !empty($menuItem['membership_tier'])) {
$membershipTier = (int) $menuItem['membership_tier'];
$hasMembershipItem = true;
break; // stop at first membership found — since list is latest first
}
}
if ($hasMembershipItem) {
// Update customer tier
$customers->update($customer_id, [
'customer_tier_id' => $membershipTier,
'customer_type' => 'VIP Customer'
]);
// Give 3 vouchers
for ($i = 0; $i < 3; $i++) {
$voucherCode = strtoupper(uniqid('VCH'));
$data = [
'customer_id' => $customer_id,
'promo_setting_id' => '29',
'voucher_order_id' => $order_id,
'voucher_topup_id' => 0,
'voucher_code' => $voucherCode,
'voucher_expiry_date' => '2025-12-31',
'voucher_status' => 'active',
];
$customer_voucher_lists->insert($data);
}
}
//send notification to customer
$wato = new Wato();
$message = "🍽️ Your order has been placed successfully!\nOur outlet is now preparing your food.\nThank you for your patience.";
$wato->pushNotification($order['phone'], $message);
return true;
//update order status
// $orders->update($order_id, ['delivery_status' => 'pending']);
}
}
if (!function_exists('calculateRoundingAmount')) {
function calculateRoundingAmount($original_amount)
{
// Convert to float and format to 2 decimal places
$original_amount = number_format_no_round((float)$original_amount);
// echo($amount);exit;
// Get the last digit (cents)
$lastDigit = (int)substr($original_amount, -1);
// Apply rounding rules
if ($lastDigit <= 2) {
// Round down to nearest 0
$amount = substr_replace($original_amount, '0', -1);
} elseif ($lastDigit <= 7) {
// Round to nearest 5
$amount = substr_replace($original_amount, '5', -1);
} else {
// Round up to next 10 cents
$amount = number_format((float)$original_amount+0.1, 2, '.', '');
$amount = substr_replace($amount, '0', -1);
}
if ($lastDigit >= 6 && $lastDigit <= 9) {
return number_format(((float)$amount - (float)$original_amount), 2);
} else {
return number_format(((float)$original_amount - (float)$amount), 2);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
use App\Models\Outlet;
if (!function_exists('checkOperationDateAndTime')) {
function checkOperationDateAndTime($outlet_id, $selected_date, $selected_time)
{
$outlet = new Outlet();
$outlet_operating_hours = $outlet->getOperatingHoursWithDays($outlet_id);
$operation_days = $outlet_operating_hours['operating_schedule']; //operation schedule and hours
$operation_date = $selected_date;
$operation_time = $selected_time.':00';
$day_of_week = date('l', strtotime($operation_date));
if(isset($operation_days[$day_of_week])){
if(!$operation_days[$day_of_week]['is_operated']){
return false;
}
$operation_hours = $operation_days[$day_of_week]['operating_hours'];
foreach($operation_hours as $operation_hour){
if($operation_time >= $operation_hour['start_time'] && $operation_time <= $operation_hour['end_time']){
return true;
}
}
return false;
}
return false;
}
}
?>
+82
View File
@@ -0,0 +1,82 @@
<?php
if(!function_exists('revertPromoSetting')){
function revertPromoSetting(array $promoSetting): array
{
$output = [
'minimumSpend' => null,
'minimumQuantity' => null,
'everyQuantity' => null,
'itemCategory1' => null,
'itemCategoryID1' => [],
'discountAmount' => null,
'discountType' => null,
'getNumber' => null,
'itemCategory2' => null,
'itemCategoryID2' => [],
'promoType' => null,
];
// --- MinimumSpend ---
$min = $promoSetting['MinimumSpend'] ?? [];
$type = $min['type'] ?? 'none';
$amountType = $min['amount_type'] ?? '';
$amount = $min['amount'] ?? 0;
if ($type !== 'none') {
$output['itemCategory1'] = $type;
$output['itemCategoryID1'] = $min['filter'] ?? [];
if ($amountType === 'amount') {
$output['minimumSpend'] = $amount;
} elseif ($amountType === 'quantity') {
$output['minimumQuantity'] = $amount;
} elseif ($amountType === 'every_quantity') {
$output['everyQuantity'] = $amount;
}
}
// --- Promo ---
$promo = $promoSetting['Promo'] ?? [];
$promoType = $promo['promo_type'] ?? '';
$output['promoType'] = $promoType;
$output['itemCategory2'] = $promo['filter_type'] ?? null;
if ($promoType === 'free_item') {
$output['itemCategoryID2'] = $promo['free_item'] ?? [];
$output['getNumber'] = $promo['amount'] ?? null;
} else {
$output['itemCategoryID2'] = $promo['filter'] ?? [];
$output['discountType'] = $promo['discount_type'] ?? null;
// Determine amount source
if ($promo['discount_type'] === 'amount' || $promo['discount_type'] === 'percentage') {
$output['discountAmount'] = $promo['amount'] ?? null;
} else {
$output['getNumber'] = $promo['amount'] ?? null;
}
}
return $output;
}
}
if(!function_exists('calculatePromoPrice')){
function calculatePromoPrice($price, $amount, $type){
if ($price < 0 || $amount < 0) {
return 0;
}
if($type == 'amount'){
return $amount;
} else if ($type == 'percentage'){
return $price * $amount / 100;
} else {
return 0;
}
}
}
?>
+27
View File
@@ -0,0 +1,27 @@
<?php
use App\Models\VoucherSchedule;
use App\Models\VoucherSettings;
if (!function_exists('getVoucherSetting')) {
function getVoucherSetting($id)
{
$voucher_settings = new VoucherSettings();
$voucher = $voucher_settings
->select('id, voucher_name')
->where('id', $id)
->where('deleted_at', null)
->first();
if (!$voucher) {
return []; // Or return null if preferred
}
return [[
'id' => $voucher['id'],
'title' => $voucher['voucher_name']
]];
}
}