add backup file and autobackup code, partial update mutations receive on transation page
This commit is contained in:
356
BACKUP_README.md
Normal file
356
BACKUP_README.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# 🗃️ CKB Database Backup & Restore Guide
|
||||
|
||||
Panduan lengkap untuk backup dan restore database Docker CKB menggunakan mysqldump.
|
||||
|
||||
## 📋 Informasi Database
|
||||
|
||||
- **Container Name**: `ckb-mysql-dev`
|
||||
- **Database Name**: `ckb_db`
|
||||
- **Database Type**: MariaDB 10.6
|
||||
- **Port**: 3306
|
||||
- **Username**: `root` / `laravel`
|
||||
- **Password**: `root` / `password`
|
||||
|
||||
## 🛠️ Available Scripts
|
||||
|
||||
### 1. **backup_db.sh** - Simple Backup
|
||||
|
||||
Script backup sederhana dengan kompresi opsional.
|
||||
|
||||
```bash
|
||||
./backup_db.sh
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✅ Backup database dengan timestamp
|
||||
- ✅ Kompresi opsional (gzip)
|
||||
- ✅ Validasi container status
|
||||
- ✅ Progress indicator
|
||||
|
||||
### 2. **restore_db.sh** - Database Restore
|
||||
|
||||
Script restore dengan interface interaktif.
|
||||
|
||||
```bash
|
||||
./restore_db.sh
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✅ List backup files yang tersedia
|
||||
- ✅ Support compressed & uncompressed files
|
||||
- ✅ Konfirmasi sebelum restore
|
||||
- ✅ Automatic decompression
|
||||
|
||||
### 3. **backup_advanced.sh** - Advanced Backup
|
||||
|
||||
Script backup lengkap dengan berbagai opsi.
|
||||
|
||||
```bash
|
||||
./backup_advanced.sh
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- ✅ Multiple backup types (Full, Structure, Data)
|
||||
- ✅ Automatic cleanup old backups
|
||||
- ✅ Colored output
|
||||
- ✅ Backup statistics
|
||||
- ✅ Compression with ratio info
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Basic Backup
|
||||
|
||||
```bash
|
||||
# Simple backup
|
||||
./backup_db.sh
|
||||
|
||||
# Manual backup command
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot ckb_db > backup.sql
|
||||
```
|
||||
|
||||
### Advanced Backup
|
||||
|
||||
```bash
|
||||
# Interactive advanced backup
|
||||
./backup_advanced.sh
|
||||
|
||||
# Full backup with all options
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--add-drop-table \
|
||||
ckb_db > full_backup.sql
|
||||
```
|
||||
|
||||
### Restore Database
|
||||
|
||||
```bash
|
||||
# Interactive restore
|
||||
./restore_db.sh
|
||||
|
||||
# Manual restore
|
||||
docker exec -i ckb-mysql-dev mysql -u root -proot ckb_db < backup.sql
|
||||
|
||||
# Restore compressed backup
|
||||
gunzip -c backup.sql.gz | docker exec -i ckb-mysql-dev mysql -u root -proot ckb_db
|
||||
```
|
||||
|
||||
## 📂 Backup Types
|
||||
|
||||
### 1. **Full Backup** (Default)
|
||||
|
||||
```bash
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--add-drop-table \
|
||||
ckb_db > full_backup.sql
|
||||
```
|
||||
|
||||
### 2. **Structure Only**
|
||||
|
||||
```bash
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--no-data \
|
||||
--routines \
|
||||
--triggers \
|
||||
ckb_db > structure_backup.sql
|
||||
```
|
||||
|
||||
### 3. **Data Only**
|
||||
|
||||
```bash
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--no-create-info \
|
||||
--single-transaction \
|
||||
ckb_db > data_backup.sql
|
||||
```
|
||||
|
||||
### 4. **Compressed Backup**
|
||||
|
||||
```bash
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot ckb_db | gzip > backup.sql.gz
|
||||
```
|
||||
|
||||
## ⚙️ Manual Commands
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker ps | grep ckb-mysql-dev
|
||||
|
||||
# Backup with timestamp
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot ckb_db > ckb_backup_$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# Backup all databases
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot --all-databases > all_databases.sql
|
||||
|
||||
# Backup specific tables
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot ckb_db table1 table2 > specific_tables.sql
|
||||
```
|
||||
|
||||
### Advanced Options
|
||||
|
||||
```bash
|
||||
# Backup with extended options
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--events \
|
||||
--add-drop-table \
|
||||
--add-drop-trigger \
|
||||
--hex-blob \
|
||||
--complete-insert \
|
||||
ckb_db > advanced_backup.sql
|
||||
|
||||
# Backup without locking tables (for MyISAM)
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--lock-tables=false \
|
||||
ckb_db > no_lock_backup.sql
|
||||
|
||||
# Backup with conditions
|
||||
docker exec ckb-mysql-dev mysqldump -u root -proot \
|
||||
--where="created_at >= '2023-01-01'" \
|
||||
ckb_db table_name > conditional_backup.sql
|
||||
```
|
||||
|
||||
## 🔄 Automated Backups
|
||||
|
||||
### Cron Job Setup
|
||||
|
||||
```bash
|
||||
# Edit crontab
|
||||
crontab -e
|
||||
|
||||
# Daily backup at 2 AM
|
||||
0 2 * * * /path/to/backup_db.sh
|
||||
|
||||
# Weekly full backup on Sunday at 3 AM
|
||||
0 3 * * 0 /path/to/backup_advanced.sh
|
||||
|
||||
# Monthly cleanup (keep last 30 days)
|
||||
0 4 1 * * find /path/to/backups -name "*.sql*" -mtime +30 -delete
|
||||
```
|
||||
|
||||
### Systemd Timer (Alternative to Cron)
|
||||
|
||||
Create `/etc/systemd/system/ckb-backup.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=CKB Database Backup
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/path/to/backup_db.sh
|
||||
User=root
|
||||
```
|
||||
|
||||
Create `/etc/systemd/system/ckb-backup.timer`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Run CKB backup daily
|
||||
Requires=ckb-backup.service
|
||||
|
||||
[Timer]
|
||||
OnCalendar=daily
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
Enable timer:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable ckb-backup.timer
|
||||
sudo systemctl start ckb-backup.timer
|
||||
```
|
||||
|
||||
## 📊 Backup Management
|
||||
|
||||
### Check Backup Size
|
||||
|
||||
```bash
|
||||
# Show backup directory size
|
||||
du -sh backups/
|
||||
|
||||
# List all backups with sizes
|
||||
ls -lah backups/
|
||||
|
||||
# Show compression ratio
|
||||
for file in backups/*.sql.gz; do
|
||||
original_size=$(gunzip -l "$file" | tail -1 | awk '{print $2}')
|
||||
compressed_size=$(stat -c%s "$file")
|
||||
ratio=$(echo "scale=1; (1 - $compressed_size/$original_size) * 100" | bc)
|
||||
echo "$(basename "$file"): ${ratio}% compression"
|
||||
done
|
||||
```
|
||||
|
||||
### Cleanup Old Backups
|
||||
|
||||
```bash
|
||||
# Delete backups older than 30 days
|
||||
find backups/ -name "*.sql*" -mtime +30 -delete
|
||||
|
||||
# Keep only last 10 backups
|
||||
ls -1t backups/ckb_backup_*.sql* | tail -n +11 | xargs rm -f
|
||||
|
||||
# Clean by size (keep if total < 1GB)
|
||||
total_size=$(du -s backups/ | cut -f1)
|
||||
if [ $total_size -gt 1048576 ]; then
|
||||
# Delete oldest files until under 1GB
|
||||
echo "Cleaning up old backups..."
|
||||
fi
|
||||
```
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Container not running**
|
||||
|
||||
```bash
|
||||
# Start container
|
||||
docker-compose up -d db
|
||||
|
||||
# Check logs
|
||||
docker logs ckb-mysql-dev
|
||||
```
|
||||
|
||||
2. **Permission denied**
|
||||
|
||||
```bash
|
||||
# Fix script permissions
|
||||
chmod +x *.sh
|
||||
|
||||
# Fix backup directory permissions
|
||||
sudo chown -R $USER:$USER backups/
|
||||
```
|
||||
|
||||
3. **Out of disk space**
|
||||
|
||||
```bash
|
||||
# Check disk usage
|
||||
df -h
|
||||
|
||||
# Clean old backups
|
||||
find backups/ -name "*.sql*" -mtime +7 -delete
|
||||
```
|
||||
|
||||
4. **MySQL connection error**
|
||||
|
||||
```bash
|
||||
# Test connection
|
||||
docker exec ckb-mysql-dev mysql -u root -proot -e "SELECT 1;"
|
||||
|
||||
# Check MySQL status
|
||||
docker exec ckb-mysql-dev mysqladmin -u root -proot status
|
||||
```
|
||||
|
||||
## 🔐 Security Notes
|
||||
|
||||
1. **Never store passwords in plain text** - Consider using MySQL config files
|
||||
2. **Encrypt sensitive backups** - Use GPG for production environments
|
||||
3. **Secure backup storage** - Store backups in secure, offsite locations
|
||||
4. **Regular restore testing** - Test backups regularly to ensure they work
|
||||
|
||||
## 📈 Best Practices
|
||||
|
||||
1. **Regular Backups**: Daily for production, weekly for development
|
||||
2. **Multiple Backup Types**: Keep both full and incremental backups
|
||||
3. **Offsite Storage**: Store backups in different physical/cloud locations
|
||||
4. **Compression**: Use gzip to save storage space
|
||||
5. **Retention Policy**: Define how long to keep backups
|
||||
6. **Monitoring**: Monitor backup success/failure
|
||||
7. **Documentation**: Document backup procedures and recovery steps
|
||||
8. **Testing**: Regularly test restore procedures
|
||||
|
||||
## 📝 File Structure
|
||||
|
||||
```
|
||||
backups/
|
||||
├── ckb_backup_20231225_143022.sql.gz # Full backup (compressed)
|
||||
├── ckb_structure_20231225_143022.sql # Structure only
|
||||
├── ckb_data_20231225_143022.sql.gz # Data only (compressed)
|
||||
└── README.md # This documentation
|
||||
|
||||
Scripts:
|
||||
├── backup_db.sh # Simple backup script
|
||||
├── backup_advanced.sh # Advanced backup with options
|
||||
├── restore_db.sh # Interactive restore script
|
||||
└── BACKUP_README.md # This documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ for CKB Project**
|
||||
@@ -288,4 +288,90 @@ class MutationsController extends Controller
|
||||
'current_stock' => $stock
|
||||
]);
|
||||
}
|
||||
|
||||
// API untuk mendapatkan mutasi yang perlu diterima oleh dealer
|
||||
public function getPendingMutations(Request $request)
|
||||
{
|
||||
$dealerId = $request->dealer_id;
|
||||
|
||||
$data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy.role'])
|
||||
->where('to_dealer_id', $dealerId)
|
||||
->where('status', 'sent')
|
||||
->select('mutations.*');
|
||||
|
||||
return DataTables::of($data)
|
||||
->addIndexColumn()
|
||||
->addColumn('mutation_number', function($row) {
|
||||
return $row->mutation_number;
|
||||
})
|
||||
->addColumn('from_dealer', function($row) {
|
||||
return $row->fromDealer->name ?? '-';
|
||||
})
|
||||
->addColumn('status', function($row) {
|
||||
$statusColor = $row->status_color;
|
||||
$statusLabel = $row->status_label;
|
||||
|
||||
$textColorClass = match($statusColor) {
|
||||
'success' => 'text-success',
|
||||
'warning' => 'text-warning',
|
||||
'danger' => 'text-danger',
|
||||
'info' => 'text-info',
|
||||
'primary' => 'text-primary',
|
||||
'brand' => 'text-primary',
|
||||
'secondary' => 'text-muted',
|
||||
default => 'text-dark'
|
||||
};
|
||||
|
||||
return "<span class=\"font-weight-bold {$textColorClass}\">{$statusLabel}</span>";
|
||||
})
|
||||
->addColumn('total_items', function($row) {
|
||||
return number_format($row->total_items, 0);
|
||||
})
|
||||
->addColumn('created_at', function($row) {
|
||||
return $row->created_at->format('d/m/Y H:i');
|
||||
})
|
||||
->addColumn('action', function($row) {
|
||||
return '<button type="button" class="btn btn-info btn-sm btn-detail" onclick="showMutationDetail('.$row->id.')">
|
||||
Detail
|
||||
</button>';
|
||||
})
|
||||
->rawColumns(['status', 'action'])
|
||||
->make(true);
|
||||
}
|
||||
|
||||
// API untuk mendapatkan detail mutasi
|
||||
public function getDetail(Mutation $mutation)
|
||||
{
|
||||
try {
|
||||
$mutation->load([
|
||||
'fromDealer',
|
||||
'toDealer',
|
||||
'requestedBy.role',
|
||||
'approvedBy.role',
|
||||
'receivedBy.role',
|
||||
'mutationDetails.product'
|
||||
]);
|
||||
|
||||
// Format created_at
|
||||
$mutation->created_at_formatted = $mutation->created_at->format('d/m/Y H:i');
|
||||
|
||||
// Add status color and label
|
||||
$mutation->status_color = $mutation->status_color;
|
||||
$mutation->status_label = $mutation->status_label;
|
||||
|
||||
// Check if can be received
|
||||
$mutation->can_be_received = $mutation->canBeReceived();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $mutation
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Gagal memuat detail mutasi: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
253
backup_advanced.sh
Executable file
253
backup_advanced.sh
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Advanced Database Backup Script for CKB Docker
|
||||
# ===============================================
|
||||
|
||||
# Configuration
|
||||
CONTAINER_NAME="ckb-mysql-dev"
|
||||
DB_NAME="ckb_db"
|
||||
DB_USER="root"
|
||||
DB_PASSWORD="root"
|
||||
BACKUP_DIR="./backups"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
RETENTION_DAYS=30 # Keep backups for 30 days
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Functions
|
||||
print_header() {
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
echo -e "${BLUE} CKB Database Advanced Backup Tool${NC}"
|
||||
echo -e "${BLUE}================================================${NC}"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
check_container() {
|
||||
if ! docker ps | grep -q $CONTAINER_NAME; then
|
||||
print_error "Container $CONTAINER_NAME is not running!"
|
||||
exit 1
|
||||
fi
|
||||
print_success "Container $CONTAINER_NAME is running"
|
||||
}
|
||||
|
||||
create_backup_dir() {
|
||||
mkdir -p $BACKUP_DIR
|
||||
print_info "Backup directory: $BACKUP_DIR"
|
||||
}
|
||||
|
||||
cleanup_old_backups() {
|
||||
print_info "Cleaning up backups older than $RETENTION_DAYS days..."
|
||||
|
||||
# Find and delete old backup files
|
||||
old_files=$(find $BACKUP_DIR -name "ckb_backup_*.sql*" -mtime +$RETENTION_DAYS 2>/dev/null)
|
||||
|
||||
if [[ -z "$old_files" ]]; then
|
||||
print_info "No old backup files found"
|
||||
else
|
||||
echo "$old_files" | while read file; do
|
||||
rm -f "$file"
|
||||
print_warning "Deleted old backup: $(basename "$file")"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
backup_full() {
|
||||
local backup_file="$BACKUP_DIR/ckb_backup_${DATE}.sql"
|
||||
print_info "Creating full backup..."
|
||||
|
||||
if docker exec $CONTAINER_NAME mysqldump -u $DB_USER -p$DB_PASSWORD \
|
||||
--single-transaction \
|
||||
--routines \
|
||||
--triggers \
|
||||
--add-drop-table \
|
||||
$DB_NAME > "$backup_file"; then
|
||||
|
||||
print_success "Full backup created: $backup_file"
|
||||
return 0
|
||||
else
|
||||
print_error "Full backup failed!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
backup_structure_only() {
|
||||
local backup_file="$BACKUP_DIR/ckb_structure_${DATE}.sql"
|
||||
print_info "Creating structure-only backup..."
|
||||
|
||||
if docker exec $CONTAINER_NAME mysqldump -u $DB_USER -p$DB_PASSWORD \
|
||||
--no-data \
|
||||
--routines \
|
||||
--triggers \
|
||||
$DB_NAME > "$backup_file"; then
|
||||
|
||||
print_success "Structure backup created: $backup_file"
|
||||
return 0
|
||||
else
|
||||
print_error "Structure backup failed!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
backup_data_only() {
|
||||
local backup_file="$BACKUP_DIR/ckb_data_${DATE}.sql"
|
||||
print_info "Creating data-only backup..."
|
||||
|
||||
if docker exec $CONTAINER_NAME mysqldump -u $DB_USER -p$DB_PASSWORD \
|
||||
--no-create-info \
|
||||
--single-transaction \
|
||||
$DB_NAME > "$backup_file"; then
|
||||
|
||||
print_success "Data backup created: $backup_file"
|
||||
return 0
|
||||
else
|
||||
print_error "Data backup failed!"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
compress_backup() {
|
||||
local file="$1"
|
||||
if [[ -f "$file" ]]; then
|
||||
print_info "Compressing backup..."
|
||||
if gzip "$file"; then
|
||||
print_success "Backup compressed: $file.gz"
|
||||
|
||||
# Show compression ratio
|
||||
original_size=$(stat -c%s "$file" 2>/dev/null || echo "0")
|
||||
compressed_size=$(stat -c%s "$file.gz" 2>/dev/null || echo "0")
|
||||
if [[ $original_size -gt 0 ]]; then
|
||||
ratio=$(echo "scale=1; (1 - $compressed_size/$original_size) * 100" | bc -l 2>/dev/null || echo "0")
|
||||
print_info "Compression ratio: ${ratio}%"
|
||||
fi
|
||||
else
|
||||
print_error "Compression failed!"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
show_backup_stats() {
|
||||
print_info "Backup Statistics:"
|
||||
echo "----------------------------------------"
|
||||
|
||||
# Count backup files
|
||||
total_backups=$(ls -1 $BACKUP_DIR/ckb_backup_*.sql* 2>/dev/null | wc -l)
|
||||
total_size=$(du -sh $BACKUP_DIR 2>/dev/null | cut -f1)
|
||||
|
||||
echo "Total backups: $total_backups"
|
||||
echo "Total size: $total_size"
|
||||
echo "Newest backup: $(ls -1t $BACKUP_DIR/ckb_backup_*.sql* 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "None")"
|
||||
echo "Oldest backup: $(ls -1tr $BACKUP_DIR/ckb_backup_*.sql* 2>/dev/null | head -1 | xargs basename 2>/dev/null || echo "None")"
|
||||
}
|
||||
|
||||
# Main Script
|
||||
print_header
|
||||
|
||||
echo "Backup Options:"
|
||||
echo "1. Full backup (structure + data)"
|
||||
echo "2. Structure only backup"
|
||||
echo "3. Data only backup"
|
||||
echo "4. All backup types"
|
||||
echo "5. Show backup statistics"
|
||||
echo "6. Cleanup old backups"
|
||||
echo "7. Exit"
|
||||
echo
|
||||
|
||||
read -p "Choose option (1-7): " -n 1 -r option
|
||||
echo
|
||||
|
||||
case $option in
|
||||
1)
|
||||
check_container
|
||||
create_backup_dir
|
||||
if backup_full; then
|
||||
backup_file="$BACKUP_DIR/ckb_backup_${DATE}.sql"
|
||||
|
||||
read -p "Compress backup? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
compress_backup "$backup_file"
|
||||
fi
|
||||
|
||||
cleanup_old_backups
|
||||
show_backup_stats
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
check_container
|
||||
create_backup_dir
|
||||
if backup_structure_only; then
|
||||
backup_file="$BACKUP_DIR/ckb_structure_${DATE}.sql"
|
||||
|
||||
read -p "Compress backup? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
compress_backup "$backup_file"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
check_container
|
||||
create_backup_dir
|
||||
if backup_data_only; then
|
||||
backup_file="$BACKUP_DIR/ckb_data_${DATE}.sql"
|
||||
|
||||
read -p "Compress backup? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
compress_backup "$backup_file"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
4)
|
||||
check_container
|
||||
create_backup_dir
|
||||
|
||||
print_info "Creating all backup types..."
|
||||
|
||||
backup_full && compress_backup "$BACKUP_DIR/ckb_backup_${DATE}.sql"
|
||||
backup_structure_only && compress_backup "$BACKUP_DIR/ckb_structure_${DATE}.sql"
|
||||
backup_data_only && compress_backup "$BACKUP_DIR/ckb_data_${DATE}.sql"
|
||||
|
||||
cleanup_old_backups
|
||||
show_backup_stats
|
||||
;;
|
||||
5)
|
||||
create_backup_dir
|
||||
show_backup_stats
|
||||
;;
|
||||
6)
|
||||
create_backup_dir
|
||||
cleanup_old_backups
|
||||
show_backup_stats
|
||||
;;
|
||||
7)
|
||||
print_info "Goodbye!"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid option!"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
print_success "Backup process completed!"
|
||||
48
backup_db.sh
Executable file
48
backup_db.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
CONTAINER_NAME="ckb-mysql-dev"
|
||||
DB_NAME="ckb_db"
|
||||
DB_USER="root"
|
||||
DB_PASSWORD="root"
|
||||
BACKUP_DIR="./backups"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="ckb_backup_${DATE}.sql"
|
||||
|
||||
# Create backup directory if it doesn't exist
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q $CONTAINER_NAME; then
|
||||
echo "Error: Container $CONTAINER_NAME is not running!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting database backup..."
|
||||
echo "Container: $CONTAINER_NAME"
|
||||
echo "Database: $DB_NAME"
|
||||
echo "Backup file: $BACKUP_DIR/$BACKUP_FILE"
|
||||
|
||||
# Create backup
|
||||
if docker exec $CONTAINER_NAME mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME > "$BACKUP_DIR/$BACKUP_FILE"; then
|
||||
echo "✅ Backup completed successfully!"
|
||||
echo "📁 File saved: $BACKUP_DIR/$BACKUP_FILE"
|
||||
|
||||
# Show file size
|
||||
FILE_SIZE=$(du -h "$BACKUP_DIR/$BACKUP_FILE" | cut -f1)
|
||||
echo "📊 File size: $FILE_SIZE"
|
||||
|
||||
# Optional: Compress the backup
|
||||
read -p "Do you want to compress the backup? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
gzip "$BACKUP_DIR/$BACKUP_FILE"
|
||||
echo "🗜️ Backup compressed: $BACKUP_DIR/$BACKUP_FILE.gz"
|
||||
fi
|
||||
|
||||
else
|
||||
echo "❌ Backup failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🎉 Backup process completed!"
|
||||
BIN
backups/ckb_backup_20250612_175715.sql.gz
Normal file
BIN
backups/ckb_backup_20250612_175715.sql.gz
Normal file
Binary file not shown.
@@ -108,6 +108,62 @@ use Illuminate\Support\Facades\Auth;
|
||||
.available-stock-mutasi {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Styles for receive mutations table */
|
||||
#receiveMutationsTable {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#receiveMutationsTable th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#receiveMutationsTable td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.btn-detail {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mutation-detail-table {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mutation-detail-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quantity-approved-input {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quantity-approved-input.is-invalid {
|
||||
border-color: #dc3545;
|
||||
background-color: #fff5f5;
|
||||
}
|
||||
|
||||
.mutation-detail-table textarea {
|
||||
font-size: 12px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.mutation-detail-table input {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@@ -487,6 +543,9 @@ use Illuminate\Support\Facades\Auth;
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#mutasi">Mutasi</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#penerimaan">Penerimaan Mutasi</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
@@ -655,6 +714,30 @@ use Illuminate\Support\Facades\Auth;
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tab Penerimaan Mutasi -->
|
||||
<div class="tab-pane" id="penerimaan" role="tabpanel">
|
||||
<div class="mt-3">
|
||||
<h6 class="mb-3">Daftar Mutasi yang Perlu Diterima</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover" id="receiveMutationsTable">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th width="20%">No. Mutasi</th>
|
||||
<th width="25%">Dealer Asal</th>
|
||||
<th width="15%">Status</th>
|
||||
<th width="10%">Total Item</th>
|
||||
<th width="15%">Tanggal</th>
|
||||
<th width="15%">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Data will be loaded via DataTables AJAX -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -668,6 +751,37 @@ use Illuminate\Support\Facades\Auth;
|
||||
<input type="hidden" name="dealer_id" value="{{ $mechanic->dealer_id }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Detail Mutasi -->
|
||||
<div class="modal fade" id="mutationDetailModal" tabindex="-1" role="dialog" aria-labelledby="mutationDetailModalLabel">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="mutationDetailModalLabel">Detail & Penerimaan Mutasi</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="receiveMutationForm" action="" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<div id="mutationDetailContent">
|
||||
<div class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Memuat data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Tutup</button>
|
||||
<button type="submit" class="btn btn-success" id="receiveButton" style="display: none;">
|
||||
<i class="fa fa-check"></i> Terima Mutasi
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('javascripts')
|
||||
@@ -1479,6 +1593,277 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
||||
|
||||
// Initialize DataTable for receive mutations
|
||||
var receiveMutationsTable;
|
||||
|
||||
function initReceiveMutationsTable() {
|
||||
if (receiveMutationsTable) {
|
||||
receiveMutationsTable.destroy();
|
||||
}
|
||||
|
||||
receiveMutationsTable = $('#receiveMutationsTable').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: {
|
||||
url: '{{ route("mutations.get-pending-mutations") }}',
|
||||
data: {
|
||||
dealer_id: {{ $mechanic->dealer_id }}
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{data: 'mutation_number', name: 'mutation_number'},
|
||||
{data: 'from_dealer', name: 'from_dealer'},
|
||||
{data: 'status', name: 'status', orderable: false},
|
||||
{data: 'total_items', name: 'total_items'},
|
||||
{data: 'created_at', name: 'created_at'},
|
||||
{data: 'action', name: 'action', orderable: false, searchable: false}
|
||||
],
|
||||
pageLength: 10,
|
||||
responsive: true,
|
||||
scrollX: true
|
||||
});
|
||||
}
|
||||
|
||||
// Show mutation detail modal
|
||||
function showMutationDetail(mutationId) {
|
||||
$('#mutationDetailModal').modal('show');
|
||||
$('#mutationDetailContent').html(`
|
||||
<div class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x"></i>
|
||||
<p class="mt-2">Memuat data...</p>
|
||||
</div>
|
||||
`);
|
||||
$('#receiveButton').hide();
|
||||
|
||||
// Load mutation detail via AJAX
|
||||
$.ajax({
|
||||
url: '{{ route("mutations.get-detail", ":id") }}'.replace(':id', mutationId),
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
renderMutationDetail(response.data);
|
||||
} else {
|
||||
$('#mutationDetailContent').html(`
|
||||
<div class="alert alert-danger">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
${response.message || 'Gagal memuat detail mutasi'}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
$('#mutationDetailContent').html(`
|
||||
<div class="alert alert-danger">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
Terjadi kesalahan saat memuat data
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Render mutation detail in modal
|
||||
function renderMutationDetail(mutation) {
|
||||
var statusColor = mutation.status_color;
|
||||
var statusLabel = mutation.status_label;
|
||||
|
||||
// Set form action URL
|
||||
$('#receiveMutationForm').attr('action', '{{ route("mutations.receive", ":id") }}'.replace(':id', mutation.id));
|
||||
|
||||
// Build detail HTML
|
||||
var detailHtml = `
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<strong>No. Mutasi:</strong><br>
|
||||
${mutation.mutation_number}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Status:</strong><br>
|
||||
<span class="font-weight-bold text-${statusColor}">${statusLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<strong>Dealer Asal:</strong><br>
|
||||
${mutation.from_dealer.name}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Tanggal:</strong><br>
|
||||
${mutation.created_at_formatted}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<strong>Diminta oleh:</strong><br>
|
||||
${mutation.requested_by ? mutation.requested_by.name : '-'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<label for="mutationNotes"><strong>Catatan Penerimaan:</strong></label>
|
||||
<textarea name="notes" id="mutationNotes" class="form-control" rows="3" placeholder="Masukkan catatan jika diperlukan..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="mb-3">Detail Produk & Penerimaan:</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered mutation-detail-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%">Produk</th>
|
||||
<th width="15%" class="text-center">Qty Diminta</th>
|
||||
<th width="20%" class="text-center">Qty Disetujui</th>
|
||||
<th width="35%">Catatan Produk</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
// Add product details with form inputs
|
||||
if (mutation.mutation_details && mutation.mutation_details.length > 0) {
|
||||
mutation.mutation_details.forEach(function(detail) {
|
||||
detailHtml += `
|
||||
<tr>
|
||||
<td>${detail.product.name}</td>
|
||||
<td class="text-center">
|
||||
<span class="font-weight-bold text-info">${parseFloat(detail.quantity_requested).toFixed(2)}</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
name="products[${detail.id}][quantity_approved]"
|
||||
class="form-control quantity-approved-input"
|
||||
min="0"
|
||||
max="${detail.quantity_requested}"
|
||||
step="0.01"
|
||||
value="${detail.quantity_requested}"
|
||||
data-max="${detail.quantity_requested}"
|
||||
placeholder="0.00">
|
||||
</td>
|
||||
<td>
|
||||
<textarea name="products[${detail.id}][notes]"
|
||||
class="form-control"
|
||||
rows="2"
|
||||
placeholder="Catatan untuk produk ini..."></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
detailHtml += `
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">Tidak ada detail produk</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
detailHtml += `
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<strong>Petunjuk:</strong>
|
||||
Masukkan quantity yang disetujui untuk setiap produk. Quantity tidak boleh melebihi quantity yang diminta.
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#mutationDetailContent').html(detailHtml);
|
||||
|
||||
// Show receive button if mutation can be received
|
||||
if (mutation.can_be_received) {
|
||||
$('#receiveButton').show();
|
||||
}
|
||||
|
||||
// Add validation for quantity inputs
|
||||
$('.quantity-approved-input').on('input', function() {
|
||||
var value = parseFloat($(this).val()) || 0;
|
||||
var max = parseFloat($(this).data('max')) || 0;
|
||||
|
||||
if (value > max) {
|
||||
$(this).addClass('is-invalid');
|
||||
if (!$(this).siblings('.invalid-feedback').length) {
|
||||
$(this).after('<div class="invalid-feedback">Quantity tidak boleh melebihi quantity yang diminta</div>');
|
||||
}
|
||||
} else {
|
||||
$(this).removeClass('is-invalid');
|
||||
$(this).siblings('.invalid-feedback').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle receive form submission
|
||||
$('#receiveMutationForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate form
|
||||
var hasInvalidInput = $('.quantity-approved-input.is-invalid').length > 0;
|
||||
if (hasInvalidInput) {
|
||||
Swal.fire({
|
||||
type: 'error',
|
||||
title: 'Validasi Gagal',
|
||||
text: 'Perbaiki quantity yang tidak valid sebelum melanjutkan'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if at least one product has quantity approved > 0
|
||||
var hasApprovedQuantity = false;
|
||||
$('.quantity-approved-input').each(function() {
|
||||
if (parseFloat($(this).val()) > 0) {
|
||||
hasApprovedQuantity = true;
|
||||
return false; // break loop
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasApprovedQuantity) {
|
||||
Swal.fire({
|
||||
type: 'warning',
|
||||
title: 'Peringatan',
|
||||
text: 'Minimal satu produk harus memiliki quantity yang disetujui'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof Swal !== 'undefined') {
|
||||
Swal.fire({
|
||||
title: 'Terima Mutasi?',
|
||||
text: "Mutasi akan diterima dengan quantity dan catatan yang telah Anda masukkan",
|
||||
type: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#28a745',
|
||||
cancelButtonColor: '#6c757d',
|
||||
confirmButtonText: 'Ya, Terima',
|
||||
cancelButtonText: 'Batal'
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
// Set loading state
|
||||
$('#receiveButton').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Memproses...');
|
||||
|
||||
// Submit form
|
||||
this.submit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (confirm('Terima mutasi dengan data yang telah dimasukkan?')) {
|
||||
$('#receiveButton').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Memproses...');
|
||||
this.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize table when tab is shown
|
||||
$('a[href="#penerimaan"]').on('shown.bs.tab', function (e) {
|
||||
setTimeout(function() {
|
||||
initReceiveMutationsTable();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
function createTransaction(form) {
|
||||
let work_ids;
|
||||
if(form == 'work') {
|
||||
|
||||
127
restore_db.sh
Executable file
127
restore_db.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
CONTAINER_NAME="ckb-mysql-dev"
|
||||
DB_NAME="ckb_db"
|
||||
DB_USER="root"
|
||||
DB_PASSWORD="root"
|
||||
BACKUP_DIR="./backups"
|
||||
|
||||
# Function to show available backups
|
||||
show_backups() {
|
||||
echo "📁 Available backup files:"
|
||||
echo "----------------------------------------"
|
||||
ls -la "$BACKUP_DIR"/*.sql* 2>/dev/null | awk '{print NR ". " $9 " (" $5 " bytes, " $6 " " $7 " " $8 ")"}'
|
||||
}
|
||||
|
||||
# Function to restore database
|
||||
restore_database() {
|
||||
local backup_file="$1"
|
||||
|
||||
echo "🔄 Starting database restore..."
|
||||
echo "Container: $CONTAINER_NAME"
|
||||
echo "Database: $DB_NAME"
|
||||
echo "Backup file: $backup_file"
|
||||
|
||||
# Check if file exists
|
||||
if [[ ! -f "$backup_file" ]]; then
|
||||
echo "❌ Error: Backup file not found: $backup_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q $CONTAINER_NAME; then
|
||||
echo "❌ Error: Container $CONTAINER_NAME is not running!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ask for confirmation
|
||||
read -p "⚠️ This will overwrite the current database. Continue? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "🚫 Restore cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if file is compressed
|
||||
if [[ "$backup_file" == *.gz ]]; then
|
||||
echo "📦 Decompressing backup file..."
|
||||
if gunzip -c "$backup_file" | docker exec -i $CONTAINER_NAME mysql -u $DB_USER -p$DB_PASSWORD $DB_NAME; then
|
||||
echo "✅ Database restored successfully from compressed backup!"
|
||||
else
|
||||
echo "❌ Restore failed!"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "📥 Restoring from uncompressed backup..."
|
||||
if docker exec -i $CONTAINER_NAME mysql -u $DB_USER -p$DB_PASSWORD $DB_NAME < "$backup_file"; then
|
||||
echo "✅ Database restored successfully!"
|
||||
else
|
||||
echo "❌ Restore failed!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main script
|
||||
echo "🗃️ CKB Database Restore Tool"
|
||||
echo "============================"
|
||||
|
||||
# Check if backup directory exists
|
||||
if [[ ! -d "$BACKUP_DIR" ]]; then
|
||||
echo "❌ Error: Backup directory not found: $BACKUP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show available backups
|
||||
show_backups
|
||||
|
||||
# Check if any backup files exist
|
||||
if ! ls "$BACKUP_DIR"/*.sql* 1> /dev/null 2>&1; then
|
||||
echo "❌ No backup files found in $BACKUP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Options:"
|
||||
echo "1. Select backup file by number"
|
||||
echo "2. Enter custom backup file path"
|
||||
echo "3. Exit"
|
||||
echo
|
||||
|
||||
read -p "Choose option (1-3): " -n 1 -r option
|
||||
echo
|
||||
|
||||
case $option in
|
||||
1)
|
||||
echo "📋 Available backups:"
|
||||
backup_files=($(ls "$BACKUP_DIR"/*.sql* 2>/dev/null))
|
||||
for i in "${!backup_files[@]}"; do
|
||||
echo "$((i+1)). $(basename "${backup_files[$i]}")"
|
||||
done
|
||||
|
||||
read -p "Enter backup number: " backup_number
|
||||
|
||||
if [[ "$backup_number" =~ ^[0-9]+$ ]] && [[ "$backup_number" -ge 1 ]] && [[ "$backup_number" -le "${#backup_files[@]}" ]]; then
|
||||
selected_backup="${backup_files[$((backup_number-1))]}"
|
||||
restore_database "$selected_backup"
|
||||
else
|
||||
echo "❌ Invalid backup number!"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
read -p "Enter backup file path: " custom_backup
|
||||
restore_database "$custom_backup"
|
||||
;;
|
||||
3)
|
||||
echo "👋 Goodbye!"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "❌ Invalid option!"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "🎉 Restore process completed!"
|
||||
@@ -246,6 +246,8 @@ Route::group(['middleware' => 'auth'], function() {
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('get-product-stock', 'getProductStock')->name('get-product-stock');
|
||||
Route::get('get-pending-mutations', 'getPendingMutations')->name('get-pending-mutations');
|
||||
Route::get('{mutation}/get-detail', 'getDetail')->name('get-detail');
|
||||
Route::get('{mutation}', 'show')->name('show');
|
||||
Route::get('{mutation}/edit', 'edit')->name('edit');
|
||||
Route::get('{mutation}/details', 'getDetails')->name('details');
|
||||
|
||||
Reference in New Issue
Block a user