Compare commits
10 Commits
573d9b7f6d
...
388d02051f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
388d02051f | ||
|
|
d0d82aa8e1 | ||
|
|
2e1ad5526c | ||
|
|
9bae910d3a | ||
|
|
3c58240206 | ||
|
|
4190c957ff | ||
|
|
7071fa2eb9 | ||
|
|
74fe447f73 | ||
|
|
b0067aeb6e | ||
|
|
64b26e494d |
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
*.md
|
||||
docker-compose.yml
|
||||
.DS_Store
|
||||
daily_order_reports/*.csv
|
||||
email.log
|
||||
23
.env.example
Normal file
23
.env.example
Normal file
@@ -0,0 +1,23 @@
|
||||
# Crew Sportswear Database
|
||||
DB_HOST_CREW=mysql
|
||||
DB_PORT_CREW=3306
|
||||
DB_NAME_CREW=custom_design
|
||||
DB_USER_CREW=crew_user
|
||||
DB_PASS_CREW=your_crew_password
|
||||
|
||||
# MerchBay Database
|
||||
DB_HOST_MERCHBAY=mysql
|
||||
DB_PORT_MERCHBAY=3306
|
||||
DB_NAME_MERCHBAY=merchbay_laravel
|
||||
DB_USER_MERCHBAY=merchbay_user
|
||||
DB_PASS_MERCHBAY=your_merchbay_password
|
||||
|
||||
# SMTP Configuration
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
|
||||
# Crew SMTP (mail@crewsportswear.com)
|
||||
SMTP_PASS_CREW=your_crew_gmail_app_password
|
||||
|
||||
# MerchBay SMTP (support@merchbay.com)
|
||||
SMTP_PASS_MERCHBAY=your_merchbay_gmail_app_password
|
||||
62
.gitea/workflows/build-push.yml
Normal file
62
.gitea/workflows/build-push.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Build and Push to Registry
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-push:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
shell: sh
|
||||
run: |
|
||||
git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo
|
||||
cd /workspace/repo
|
||||
git checkout $GITHUB_REF_NAME
|
||||
|
||||
- name: Extract version from tag
|
||||
shell: sh
|
||||
run: |
|
||||
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Docker image
|
||||
shell: sh
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
docker build -t email_reports_crew:${VERSION} .
|
||||
docker tag email_reports_crew:${VERSION} email_reports_crew:latest
|
||||
|
||||
- name: Login to Docker Registry
|
||||
shell: sh
|
||||
env:
|
||||
REGISTRY_URL: ${{ secrets.REGISTRY_URL }}
|
||||
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin $REGISTRY_URL
|
||||
|
||||
- name: Tag and Push images
|
||||
shell: sh
|
||||
env:
|
||||
REGISTRY_URL: ${{ secrets.REGISTRY_URL }}
|
||||
run: |
|
||||
docker tag email_reports_crew:${VERSION} ${REGISTRY_URL}/email_reports_crew:${VERSION}
|
||||
docker tag email_reports_crew:${VERSION} ${REGISTRY_URL}/email_reports_crew:latest
|
||||
|
||||
docker push ${REGISTRY_URL}/email_reports_crew:${VERSION}
|
||||
docker push ${REGISTRY_URL}/email_reports_crew:latest
|
||||
|
||||
echo "✓ Pushed images:"
|
||||
echo " ${REGISTRY_URL}/email_reports_crew:${VERSION}"
|
||||
echo " ${REGISTRY_URL}/email_reports_crew:latest"
|
||||
|
||||
- name: Cleanup
|
||||
shell: sh
|
||||
run: |
|
||||
docker image prune -af
|
||||
echo "✓ Cleanup completed"
|
||||
168
.gitea/workflows/deploy.yml
Normal file
168
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,168 @@
|
||||
name: Deploy Production Email Reports (Unified)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
shell: sh
|
||||
run: |
|
||||
git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git /workspace/repo
|
||||
cd /workspace/repo
|
||||
git checkout $GITHUB_REF_NAME
|
||||
|
||||
- name: Build Docker image
|
||||
shell: sh
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
docker build -t email_reports_unified:latest .
|
||||
docker save email_reports_unified:latest | gzip > email_reports_unified.tar.gz
|
||||
|
||||
- name: Setup SSH
|
||||
shell: sh
|
||||
env:
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Upload image and compose
|
||||
shell: sh
|
||||
env:
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
run: |
|
||||
scp -i ~/.ssh/id_ed25519 \
|
||||
/workspace/repo/email_reports_unified.tar.gz \
|
||||
/workspace/repo/docker-compose.yml \
|
||||
${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/
|
||||
|
||||
- name: Deploy on server
|
||||
shell: sh
|
||||
env:
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_ed25519 $DEPLOY_USER@$DEPLOY_HOST << 'EOF'
|
||||
set -e
|
||||
|
||||
DEPLOY_DIR="/var/www/apps/email_reports"
|
||||
sudo mkdir -p "$DEPLOY_DIR"
|
||||
sudo chown $USER:$USER "$DEPLOY_DIR"
|
||||
|
||||
echo "Loading image"
|
||||
docker load < /tmp/email_reports_unified.tar.gz
|
||||
|
||||
echo "Removing old email_reports images"
|
||||
docker images | grep email_reports_unified | grep -v "$(docker images email_reports_unified:latest -q)" | awk '{print $3}' | xargs -r docker rmi -f || true
|
||||
|
||||
echo "Updating compose file"
|
||||
cp /tmp/docker-compose.yml "$DEPLOY_DIR/docker-compose.yml"
|
||||
|
||||
cd "$DEPLOY_DIR"
|
||||
|
||||
echo "Checking .env file"
|
||||
if [ ! -f .env ]; then
|
||||
echo ".env file not found at $DEPLOY_DIR/.env"
|
||||
echo "Please create it first with required variables:"
|
||||
echo " Crew: DB_HOST_CREW, DB_NAME_CREW, DB_USER_CREW, DB_PASS_CREW, SMTP_PASS_CREW"
|
||||
echo " MerchBay: DB_HOST_MERCHBAY, DB_NAME_MERCHBAY, DB_USER_MERCHBAY, DB_PASS_MERCHBAY, SMTP_PASS_MERCHBAY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Fixing .env permissions"
|
||||
sudo chown $USER:$USER .env
|
||||
sudo chmod 600 .env
|
||||
|
||||
echo "Ensure networks"
|
||||
docker network inspect crew-app-net >/dev/null 2>&1 || \
|
||||
docker network create crew-app-net
|
||||
|
||||
echo "Creating required directories"
|
||||
mkdir -p daily_order_reports_crew daily_order_reports_merchbay
|
||||
touch email.log
|
||||
chmod 666 email.log
|
||||
chmod 755 daily_order_reports_crew daily_order_reports_merchbay
|
||||
|
||||
echo "Stopping existing containers"
|
||||
docker compose down || true
|
||||
docker rm -f email_reports_unified email_reports_crew email_reports_merchbay || true
|
||||
|
||||
echo "Starting unified container (env vars from .env file)"
|
||||
docker compose up -d
|
||||
|
||||
echo "Waiting for container to start"
|
||||
sleep 10
|
||||
|
||||
if docker ps --format '{{.Names}}' | grep -q email_reports_unified; then
|
||||
echo "✓ Container is running"
|
||||
echo "Testing cron daemon"
|
||||
docker exec email_reports_unified ps aux | grep -q crond && echo "✓ Cron is running"
|
||||
|
||||
echo "Cron schedule:"
|
||||
docker exec email_reports_unified crontab -l
|
||||
|
||||
echo "Testing database connectivity (Crew)"
|
||||
docker exec email_reports_unified ping -c 1 mysql && echo "✓ Can reach MySQL"
|
||||
else
|
||||
echo "✗ Container failed to start"
|
||||
docker compose logs
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Cleanup"
|
||||
rm -f /tmp/email_reports_unified.tar.gz /tmp/docker-compose.yml
|
||||
|
||||
echo "Docker cleanup"
|
||||
docker image prune -af --filter "until=24h" || true
|
||||
docker container prune -f || true
|
||||
docker system df
|
||||
|
||||
echo "✓ Deployment completed!"
|
||||
echo "Email reports container: email_reports_unified"
|
||||
echo "Next scheduled runs:"
|
||||
echo " - Crew: 23:55 CT"
|
||||
echo " - MerchBay: 23:56 CT"
|
||||
EOF
|
||||
|
||||
- name: Verify deployment
|
||||
shell: sh
|
||||
env:
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_ed25519 $DEPLOY_USER@$DEPLOY_HOST << 'EOF'
|
||||
echo "Container status:"unified --format "table {{.Names}}\t{{.Status}}\t{{.State}}"
|
||||
|
||||
echo ""
|
||||
echo "Recent logs:"
|
||||
docker logs --tail 20 email_reports_unified
|
||||
|
||||
echo ""
|
||||
echo "To test manually:"
|
||||
echo " Crew: docker exec email_reports_unified BRAND=crew php /app/send_report.php"
|
||||
echo " MerchBay: docker exec email_reports_unified BRAND=merchbay php /app/send_report.php"
|
||||
echo ""
|
||||
echo "To view logs:"
|
||||
echo " docker logs -f email_reports_unified"
|
||||
echo " docker exec email_reports_unified tail -f /app/email.log"
|
||||
echo ""
|
||||
echo "Filter by brand:"
|
||||
echo " docker exec email_reports_unified grep '[CREW]' /app/email.log"
|
||||
echo " docker exec email_reports_unified grep '[MERCHBAY]'
|
||||
echo " docker exec email_reports_crew tail -f /app/email.log"
|
||||
EOF
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.env
|
||||
email.log
|
||||
daily_order_reports/
|
||||
daily_order_reports_crew/
|
||||
daily_order_reports_merchbay/
|
||||
.DS_Store
|
||||
phpmailer/
|
||||
417
DEPLOYMENT.md
Normal file
417
DEPLOYMENT.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# Email Reports Deployment Checklist
|
||||
|
||||
## Pre-Deployment Setup
|
||||
|
||||
### 1. Configure Gitea Secrets
|
||||
|
||||
In your Gitea repository **Settings → Secrets**, add:
|
||||
|
||||
```
|
||||
DEPLOY_SSH_KEY = <your-ssh-private-key>
|
||||
DEPLOY_HOST = <server-ip-or-hostname>
|
||||
DEPLOY_USER = <ssh-username>
|
||||
REGISTRY_URL = <docker-registry-url> (optional, for version tags)
|
||||
REGISTRY_USER = <registry-username> (optional)
|
||||
REGISTRY_PASSWORD = <registry-password> (optional)
|
||||
```
|
||||
|
||||
### 2. Server Preparation
|
||||
|
||||
SSH into your production server:
|
||||
|
||||
```bash
|
||||
ssh user@your-server
|
||||
|
||||
# Create deployment directory
|
||||
sudo mkdir -p /var/www/apps/email_reports
|
||||
sudo chown $USER:$USER /var/www/apps/email_reports
|
||||
cd /var/www/apps/email_reports
|
||||
|
||||
# Create .env file with production values
|
||||
vim .env
|
||||
```
|
||||
|
||||
**Required .env variables:**
|
||||
```env
|
||||
DB_HOST=mysql # Your MySQL container name
|
||||
DB_PORT=3306
|
||||
DB_NAME=custom_design
|
||||
DB_USER=crew_user
|
||||
DB_PASS=your_secure_password # CHANGE THIS
|
||||
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=mail@crewsportswear.com
|
||||
SMTP_PASS=your_gmail_app_password # Get from Gmail
|
||||
|
||||
EMAIL_TO=graphics@crewsportswear.com
|
||||
EMAIL_BCC=webmaster@crewsportswear.com,angelo@crewsportswear.com,production@crewsportswear.com
|
||||
|
||||
APP_URL=https://www.crewsportswear.com
|
||||
ADMIN_URL=https://admin.crewsportswear.app
|
||||
```
|
||||
|
||||
```bash
|
||||
# Secure .env
|
||||
chmod 600 .env
|
||||
|
||||
# Create data directories
|
||||
mkdir -p daily_order_reports
|
||||
touch email.log
|
||||
chmod 666 email.log
|
||||
|
||||
# Ensure Docker network exists
|
||||
docker network create crew-app-net || true
|
||||
```
|
||||
|
||||
### 3. Verify MySQL Access
|
||||
|
||||
Test database connectivity from server:
|
||||
|
||||
```bash
|
||||
# If MySQL is in a container:
|
||||
docker exec mysql_container_name mysql -u crew_user -p -e "SHOW DATABASES;"
|
||||
|
||||
# Verify network connectivity
|
||||
docker run --rm --network crew-app-net alpine ping -c 3 mysql
|
||||
```
|
||||
|
||||
### 4. Get Gmail App Password
|
||||
|
||||
1. Go to https://myaccount.google.com/apppasswords
|
||||
2. Create new app password for "Email Reports"
|
||||
3. Copy the 16-character password
|
||||
4. Add to `.env` as `SMTP_PASS`
|
||||
|
||||
---
|
||||
|
||||
## Deployment Workflow
|
||||
|
||||
### Option 1: Automated (Recommended)
|
||||
|
||||
```bash
|
||||
# From your local machine
|
||||
cd email_reports
|
||||
|
||||
git add .
|
||||
git commit -m "Deploy email reports container"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Gitea Actions will automatically:
|
||||
- ✅ Build Docker image
|
||||
- ✅ Transfer to server
|
||||
- ✅ Deploy to /var/www/apps/email_reports
|
||||
- ✅ Start container with cron
|
||||
- ✅ Verify deployment
|
||||
|
||||
Watch the workflow in Gitea: **Actions** tab
|
||||
|
||||
### Option 2: Manual Deployment
|
||||
|
||||
SSH to server and deploy manually:
|
||||
|
||||
```bash
|
||||
cd /var/www/apps/email_reports
|
||||
|
||||
# Pull latest code (if using git on server)
|
||||
git pull
|
||||
|
||||
# Or copy files manually via scp
|
||||
scp docker-compose.yml user@server:/var/www/apps/email_reports/
|
||||
|
||||
# Build and deploy
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
|
||||
# Verify
|
||||
docker ps | grep email_reports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post-Deployment Verification
|
||||
|
||||
### 1. Check Container Status
|
||||
|
||||
```bash
|
||||
# Container is running
|
||||
docker ps --filter name=email_reports_crew
|
||||
|
||||
# Expected output:
|
||||
# CONTAINER ID IMAGE STATUS NAMES
|
||||
# abc123... email_reports_crew:latest Up 2 minutes email_reports_crew
|
||||
```
|
||||
|
||||
### 2. Verify Cron is Running
|
||||
|
||||
```bash
|
||||
docker exec email_reports_crew ps aux | grep crond
|
||||
|
||||
# Should show: crond -f -l 2
|
||||
```
|
||||
|
||||
### 3. Check Cron Schedule
|
||||
|
||||
```bash
|
||||
docker exec email_reports_crew crontab -l
|
||||
|
||||
# Expected output:
|
||||
# 55 23 * * * cd /app && php index.php >> /var/log/cron.log 2>&1
|
||||
```
|
||||
|
||||
### 4. Test Database Connection
|
||||
|
||||
```bash
|
||||
docker exec email_reports_crew php -r "
|
||||
\$host = getenv('DB_HOST');
|
||||
\$db = getenv('DB_NAME');
|
||||
\$user = getenv('DB_USER');
|
||||
\$pass = getenv('DB_PASS');
|
||||
new PDO(\"mysql:host=\$host;dbname=\$db\", \$user, \$pass);
|
||||
echo 'Database connection: OK\n';
|
||||
"
|
||||
```
|
||||
|
||||
### 5. Run Manual Test
|
||||
|
||||
```bash
|
||||
# Execute report immediately (don't wait for cron)
|
||||
docker exec email_reports_crew php /app/index.php
|
||||
|
||||
# Check output
|
||||
docker exec email_reports_crew cat /app/email.log | tail -5
|
||||
```
|
||||
|
||||
Expected log entry:
|
||||
```
|
||||
2026-01-02 15:30:00 successfully sent
|
||||
```
|
||||
OR
|
||||
```
|
||||
2026-01-02 15:30:00 No order for today
|
||||
```
|
||||
|
||||
### 6. Verify Email Sent
|
||||
|
||||
Check your inbox at `graphics@crewsportswear.com` for test email.
|
||||
|
||||
### 7. Check Logs
|
||||
|
||||
```bash
|
||||
# Container logs
|
||||
docker logs email_reports_crew
|
||||
|
||||
# Cron logs
|
||||
docker exec email_reports_crew tail -f /var/log/cron.log
|
||||
|
||||
# Email execution logs
|
||||
docker exec email_reports_crew tail -f /app/email.log
|
||||
|
||||
# CSV files generated
|
||||
docker exec email_reports_crew ls -lh /app/daily_order_reports/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### View Real-time Logs
|
||||
|
||||
```bash
|
||||
# Follow cron execution
|
||||
docker logs -f email_reports_crew
|
||||
|
||||
# Follow email log
|
||||
docker exec email_reports_crew tail -f /app/email.log
|
||||
```
|
||||
|
||||
### Check Next Scheduled Run
|
||||
|
||||
Cron runs at **23:55 Central Time daily**
|
||||
|
||||
```bash
|
||||
# Current time in container
|
||||
docker exec email_reports_crew date
|
||||
|
||||
# Should show: America/Chicago timezone
|
||||
docker exec email_reports_crew cat /etc/timezone
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
# Container health status
|
||||
docker inspect email_reports_crew | grep -A 5 Health
|
||||
|
||||
# Manual health check
|
||||
docker exec email_reports_crew ps aux | grep crond
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container Won't Start
|
||||
|
||||
```bash
|
||||
# View startup logs
|
||||
docker logs email_reports_crew
|
||||
|
||||
# Check compose file
|
||||
cd /var/www/apps/email_reports
|
||||
cat docker-compose.yml
|
||||
|
||||
# Verify .env exists
|
||||
ls -la .env
|
||||
```
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Test network connectivity
|
||||
docker exec email_reports_crew ping mysql
|
||||
|
||||
# Verify database credentials in .env
|
||||
docker exec email_reports_crew env | grep DB_
|
||||
```
|
||||
|
||||
### Email Not Sending
|
||||
|
||||
```bash
|
||||
# Enable debug mode
|
||||
docker exec email_reports_crew sed -i 's|// \$mail->SMTPDebug = 3;|\$mail->SMTPDebug = 3;|' /app/index.php
|
||||
|
||||
# Run manually with debug output
|
||||
docker exec email_reports_crew php /app/index.php
|
||||
|
||||
# Check SMTP credentials
|
||||
docker exec email_reports_crew env | grep SMTP_
|
||||
```
|
||||
|
||||
### Cron Not Running
|
||||
|
||||
```bash
|
||||
# Check cron daemon
|
||||
docker exec email_reports_crew ps aux | grep crond
|
||||
|
||||
# Restart container
|
||||
docker restart email_reports_crew
|
||||
|
||||
# Verify crontab
|
||||
docker exec email_reports_crew crontab -l
|
||||
```
|
||||
|
||||
### Wrong Timezone
|
||||
|
||||
```bash
|
||||
# Check timezone
|
||||
docker exec email_reports_crew date
|
||||
docker exec email_reports_crew cat /etc/timezone
|
||||
|
||||
# Should be: America/Chicago
|
||||
# If not, rebuild image
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If deployment fails:
|
||||
|
||||
```bash
|
||||
cd /var/www/apps/email_reports
|
||||
|
||||
# Stop and remove container
|
||||
docker-compose down
|
||||
|
||||
# Load previous image (if kept)
|
||||
docker load < /tmp/email_reports_crew_backup.tar.gz
|
||||
|
||||
# Or pull from registry
|
||||
docker pull your-registry/email_reports_crew:previous-version
|
||||
|
||||
# Start with old image
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
- [ ] Gitea secrets configured
|
||||
- [ ] Server directory created: `/var/www/apps/email_reports`
|
||||
- [ ] `.env` file created with all variables
|
||||
- [ ] Database credentials tested
|
||||
- [ ] Gmail app password obtained and configured
|
||||
- [ ] Docker network `crew-app-net` exists
|
||||
- [ ] Code pushed to `main` branch (triggers workflow)
|
||||
- [ ] Workflow completed successfully (green checkmark)
|
||||
- [ ] Container is running: `docker ps | grep email_reports`
|
||||
- [ ] Cron is running: `ps aux | grep crond`
|
||||
- [ ] Manual test executed successfully
|
||||
- [ ] Email received by recipients
|
||||
- [ ] Logs showing successful execution
|
||||
- [ ] Scheduled for next automatic run at 23:55 CT
|
||||
|
||||
---
|
||||
|
||||
## For MerchBay Reports
|
||||
|
||||
Repeat the same process in a separate directory:
|
||||
|
||||
```bash
|
||||
# On server
|
||||
sudo mkdir -p /var/www/apps/email_reports_merchbay
|
||||
# ... follow same steps with merchbay database/credentials
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Update Recipients
|
||||
|
||||
```bash
|
||||
# Edit .env on server
|
||||
cd /var/www/apps/email_reports
|
||||
vim .env
|
||||
|
||||
# Update EMAIL_TO or EMAIL_BCC
|
||||
# Then restart container
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
### Change Schedule
|
||||
|
||||
Edit Dockerfile, rebuild, and redeploy:
|
||||
|
||||
```dockerfile
|
||||
# Change from 23:55 to 22:00
|
||||
RUN echo "0 22 * * * cd /app && php index.php" > /etc/crontabs/root
|
||||
```
|
||||
|
||||
### View Historical Reports
|
||||
|
||||
```bash
|
||||
# List all generated CSVs
|
||||
docker exec email_reports_crew ls -lh /app/daily_order_reports/
|
||||
|
||||
# Download specific report
|
||||
docker cp email_reports_crew:/app/daily_order_reports/daily_order_report_2026-01-02.csv .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ Container running continuously
|
||||
✅ Cron daemon active
|
||||
✅ Database connectivity confirmed
|
||||
✅ Manual test sends email
|
||||
✅ Recipients receive reports
|
||||
✅ CSV files generated in `/app/daily_order_reports/`
|
||||
✅ Logs show execution history
|
||||
✅ Scheduled to run at 23:55 CT daily
|
||||
|
||||
**Deployment Complete! 🎉**
|
||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
||||
FROM php:7.4-cli-alpine
|
||||
|
||||
# Install dependencies
|
||||
RUN apk add --no-cache \
|
||||
dcron \
|
||||
mysql-client \
|
||||
&& docker-php-ext-install pdo pdo_mysql
|
||||
|
||||
# Set timezone
|
||||
RUN apk add --no-cache tzdata && \
|
||||
cp /usr/share/zoneinfo/America/Chicago /etc/localtime && \
|
||||
echo "America/Chicago" > /etc/timezone && \
|
||||
apk del tzdata
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy email reports scripts
|
||||
COPY . /app/
|
||||
|
||||
# Create reports directories
|
||||
RUN mkdir -p /app/daily_order_reports_crew /app/daily_order_reports_merchbay && \
|
||||
chmod 755 /app/daily_order_reports_crew /app/daily_order_reports_merchbay
|
||||
|
||||
# Create crontab with both reports
|
||||
RUN echo "55 23 * * * BRAND=crew php /app/send_report.php >> /var/log/cron.log 2>&1" > /etc/crontabs/root && \
|
||||
echo "56 23 * * * BRAND=merchbay php /app/send_report.php >> /var/log/cron.log 2>&1" >> /etc/crontabs/root && \
|
||||
chmod 0644 /etc/crontabs/root
|
||||
|
||||
# Create log file
|
||||
RUN touch /var/log/cron.log && \
|
||||
chmod 666 /var/log/cron.log
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD ps aux | grep -v grep | grep -q crond || exit 1
|
||||
|
||||
# Start cron in foreground
|
||||
CMD crond -f -l 2
|
||||
324
MIGRATION.md
Normal file
324
MIGRATION.md
Normal file
@@ -0,0 +1,324 @@
|
||||
# Migration to Unified Email Reports
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **Merged `email_reports` and `email_reports_merchbay` into ONE container**
|
||||
|
||||
**Before:**
|
||||
- Two separate directories
|
||||
- Two separate containers
|
||||
- Two separate cron jobs calling via HTTP
|
||||
|
||||
**After:**
|
||||
- Single unified container
|
||||
- Handles both Crew & MerchBay
|
||||
- Two cron jobs (23:55, 23:56) running internally
|
||||
- No HTTP calls needed
|
||||
|
||||
---
|
||||
|
||||
## What Changed
|
||||
|
||||
### New Unified Architecture
|
||||
|
||||
```
|
||||
email_reports/
|
||||
├── send_report.php # NEW: Unified script for both brands
|
||||
├── index.php # KEPT: Legacy Crew script (backward compatible)
|
||||
├── Dockerfile # UPDATED: Runs both cron jobs
|
||||
├── docker-compose.yml # UPDATED: Unified container config
|
||||
├── .env.example # UPDATED: Both Crew + MerchBay vars
|
||||
└── .gitea/workflows/
|
||||
└── deploy.yml # UPDATED: Deploys unified container
|
||||
```
|
||||
|
||||
### Key Changes
|
||||
|
||||
1. **send_report.php** - Brand-agnostic report generator
|
||||
- Uses `BRAND` environment variable (`crew` or `merchbay`)
|
||||
- Auto-selects database, SMTP, recipients per brand
|
||||
- Writes to separate CSV directories
|
||||
|
||||
2. **Cron Jobs** - Two jobs in one container:
|
||||
```cron
|
||||
55 23 * * * BRAND=crew php /app/send_report.php
|
||||
56 23 * * * BRAND=merchbay php /app/send_report.php
|
||||
```
|
||||
|
||||
3. **Separate CSV Directories**:
|
||||
- `daily_order_reports_crew/`
|
||||
- `daily_order_reports_merchbay/`
|
||||
|
||||
4. **Unified Logs** - Single `email.log` with brand prefixes:
|
||||
```
|
||||
2026-01-02 23:55:00 [CREW] successfully sent (5 orders)
|
||||
2026-01-02 23:56:00 [MERCHBAY] No order for today
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### 1. Server Preparation
|
||||
|
||||
SSH to your production server:
|
||||
|
||||
```bash
|
||||
ssh user@server
|
||||
cd /var/www/apps/email_reports
|
||||
|
||||
# Backup existing setup
|
||||
tar -czf email_reports_backup_$(date +%Y%m%d).tar.gz \
|
||||
daily_order_reports/ email.log .env
|
||||
|
||||
# Create new directories
|
||||
mkdir -p daily_order_reports_crew daily_order_reports_merchbay
|
||||
|
||||
# Move existing Crew reports (optional)
|
||||
mv daily_order_reports/* daily_order_reports_crew/ 2>/dev/null || true
|
||||
```
|
||||
|
||||
### 2. Update .env File
|
||||
|
||||
Replace your `.env` with unified configuration:
|
||||
|
||||
```bash
|
||||
cd /var/www/apps/email_reports
|
||||
cat > .env << 'EOL'
|
||||
# Crew Sportswear Database
|
||||
DB_HOST_CREW=mysql
|
||||
DB_PORT_CREW=3306
|
||||
DB_NAME_CREW=custom_design
|
||||
DB_USER_CREW=crew_user
|
||||
DB_PASS_CREW=your_crew_password
|
||||
|
||||
# MerchBay Database
|
||||
DB_HOST_MERCHBAY=mysql
|
||||
DB_PORT_MERCHBAY=3306
|
||||
DB_NAME_MERCHBAY=merchbay_laravel
|
||||
DB_USER_MERCHBAY=merchbay_user
|
||||
DB_PASS_MERCHBAY=your_merchbay_password
|
||||
|
||||
# SMTP Configuration
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
|
||||
# Crew SMTP (mail@crewsportswear.com)
|
||||
SMTP_PASS_CREW=your_crew_gmail_app_password
|
||||
|
||||
# MerchBay SMTP (support@merchbay.com)
|
||||
SMTP_PASS_MERCHBAY=your_merchbay_gmail_app_password
|
||||
EOL
|
||||
|
||||
chmod 600 .env
|
||||
```
|
||||
|
||||
### 3. Remove Old Cron Jobs
|
||||
|
||||
If you had host-level cron jobs calling via HTTP:
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
|
||||
# REMOVE these lines:
|
||||
# 55 23 * * * wget -qO- https://www.crewsportswear.com/email_reports/index.php &> /dev/null
|
||||
# 55 23 * * * wget -qO- https://www.crewsportswear.com/email_reports_merchbay/index.php &> /dev/null
|
||||
```
|
||||
|
||||
**The container now handles scheduling internally!**
|
||||
|
||||
### 4. Deploy Unified Container
|
||||
|
||||
```bash
|
||||
# From your local machine
|
||||
cd email_reports
|
||||
git add .
|
||||
git commit -m "Migrate to unified email reports container"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Gitea workflow will automatically:
|
||||
- Build unified image
|
||||
- Deploy to server
|
||||
- Start container with both cron jobs
|
||||
|
||||
### 5. Verify Deployment
|
||||
|
||||
```bash
|
||||
# On server
|
||||
docker ps | grep email_reports
|
||||
|
||||
# Should show:
|
||||
# email_reports_unified Up X minutes
|
||||
|
||||
# Check cron schedule
|
||||
docker exec email_reports_unified crontab -l
|
||||
|
||||
# Expected output:
|
||||
# 55 23 * * * BRAND=crew php /app/send_report.php >> /var/log/cron.log 2>&1
|
||||
# 56 23 * * * BRAND=merchbay php /app/send_report.php >> /var/log/cron.log 2>&1
|
||||
```
|
||||
|
||||
### 6. Test Both Reports
|
||||
|
||||
```bash
|
||||
# Test Crew report
|
||||
docker exec email_reports_unified BRAND=crew php /app/send_report.php
|
||||
|
||||
# Test MerchBay report
|
||||
docker exec email_reports_unified BRAND=merchbay php /app/send_report.php
|
||||
|
||||
# Check logs
|
||||
docker exec email_reports_unified tail /app/email.log
|
||||
|
||||
# Expected:
|
||||
# 2026-01-02 15:30:00 [CREW] successfully sent
|
||||
# 2026-01-02 15:30:15 [MERCHBAY] successfully sent
|
||||
```
|
||||
|
||||
### 7. Verify Email Delivery
|
||||
|
||||
- Check `graphics@crewsportswear.com` inbox
|
||||
- Should receive **two separate emails**:
|
||||
- "CREW Daily Order Report - 2026-01-02"
|
||||
- "Merchbay Daily Order Report - 2026-01-02"
|
||||
|
||||
---
|
||||
|
||||
## What to Delete
|
||||
|
||||
### On Server
|
||||
|
||||
```bash
|
||||
# The old email_reports_merchbay directory is no longer needed
|
||||
# (if it exists on the server)
|
||||
rm -rf /var/www/apps/email_reports_merchbay
|
||||
|
||||
# Old container (will be removed automatically by deployment)
|
||||
docker rm -f email_reports_crew email_reports_merchbay
|
||||
```
|
||||
|
||||
### In Git Repository
|
||||
|
||||
The `email_reports_merchbay/` directory in your repos can stay for reference, or archive it:
|
||||
|
||||
```bash
|
||||
cd /path/to/repos
|
||||
tar -czf email_reports_merchbay_archive.tar.gz email_reports_merchbay/
|
||||
# Optional: rm -rf email_reports_merchbay/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Both Reports Not Running
|
||||
|
||||
```bash
|
||||
# Check crontab
|
||||
docker exec email_reports_unified crontab -l
|
||||
|
||||
# Manually trigger both
|
||||
docker exec email_reports_unified BRAND=crew php /app/send_report.php
|
||||
docker exec email_reports_unified BRAND=merchbay php /app/send_report.php
|
||||
```
|
||||
|
||||
### MerchBay Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Verify MerchBay DB credentials in .env
|
||||
docker exec email_reports_unified env | grep MERCHBAY
|
||||
|
||||
# Test connection
|
||||
docker exec email_reports_unified php -r "
|
||||
new PDO(
|
||||
'mysql:host=' . getenv('DB_HOST_MERCHBAY') . ';dbname=' . getenv('DB_NAME_MERCHBAY'),
|
||||
getenv('DB_USER_MERCHBAY'),
|
||||
getenv('DB_PASS_MERCHBAY')
|
||||
);
|
||||
echo 'MerchBay DB: OK\n';
|
||||
"
|
||||
```
|
||||
|
||||
### Only One Email Received
|
||||
|
||||
```bash
|
||||
# Check which brand succeeded
|
||||
docker exec email_reports_unified grep "$(date +%Y-%m-%d)" /app/email.log
|
||||
|
||||
# Look for [CREW] or [MERCHBAY] prefixes
|
||||
# If one failed, check the error message
|
||||
```
|
||||
|
||||
### CSV Files Not Created
|
||||
|
||||
```bash
|
||||
# Check directories exist
|
||||
docker exec email_reports_unified ls -la /app/ | grep daily_order
|
||||
|
||||
# Should show:
|
||||
# daily_order_reports_crew/
|
||||
# daily_order_reports_merchbay/
|
||||
|
||||
# Check permissions
|
||||
docker exec email_reports_unified stat -c '%a' /app/daily_order_reports_crew
|
||||
# Should be 755
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If something goes wrong:
|
||||
|
||||
```bash
|
||||
# On server
|
||||
cd /var/www/apps/email_reports
|
||||
|
||||
# Stop unified container
|
||||
docker compose down
|
||||
|
||||
# Restore backup
|
||||
tar -xzf email_reports_backup_YYYYMMDD.tar.gz
|
||||
|
||||
# Restore old .env
|
||||
# Start old container
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits of Unified Container
|
||||
|
||||
✅ **Simpler Infrastructure**
|
||||
- One container instead of two
|
||||
- Single deployment process
|
||||
- Shared codebase
|
||||
|
||||
✅ **Better Resource Usage**
|
||||
- One cron daemon
|
||||
- Shared Docker image
|
||||
- Less memory overhead
|
||||
|
||||
✅ **Easier Maintenance**
|
||||
- Update both brands at once
|
||||
- Unified logging
|
||||
- Single point of monitoring
|
||||
|
||||
✅ **Consistent Behavior**
|
||||
- Same PHPMailer version
|
||||
- Same PHP version
|
||||
- Same timezone handling
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Deploy unified container
|
||||
2. ✅ Test both reports manually
|
||||
3. ✅ Wait for automatic execution at 23:55/23:56
|
||||
4. ✅ Verify emails received
|
||||
5. ✅ Monitor logs for first week
|
||||
6. ✅ Remove old cron jobs
|
||||
7. ✅ Archive old email_reports_merchbay directory
|
||||
|
||||
**Migration Complete! 🎉**
|
||||
467
README.md
Normal file
467
README.md
Normal file
@@ -0,0 +1,467 @@
|
||||
# Email Reports - Dockerized Container
|
||||
|
||||
Unified Docker container for automated daily order email reports for **both Crew Sportswear and MerchBay**.
|
||||
|
||||
## Overview
|
||||
|
||||
This container runs cron jobs daily at **23:55 CT** to:
|
||||
1. Query orders from both Crew and MerchBay databases
|
||||
2. Generate separate CSV reports for each brand
|
||||
3. Email reports to recipients via SMTP
|
||||
|
||||
**Cron Schedule:**
|
||||
- **23:55** - Crew Sportswear report
|
||||
- **23:56** - MerchBay report
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create `.env` file
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
vim .env
|
||||
```
|
||||
|
||||
Configure your environment variables:
|
||||
```env
|
||||
# Crew Database
|
||||
DB_HOST_CREW=mysql
|
||||
DB_NAME_CREW=custom_design
|
||||
DB_USER_CREW=crew_user
|
||||
DB_PASS_CREW=your_password
|
||||
|
||||
# MerchBay Database
|
||||
DB_HOST_MERCHBAY=mysql
|
||||
DB_NAME_MERCHBAY=merchbay_laravel
|
||||
DB_USER_MERCHBAY=merchbay_user
|
||||
DB_PASS_MERCHBAY=your_password
|
||||
|
||||
# SMTP
|
||||
SMTP_PASS_CREW=crew_gmail_app_password
|
||||
SMTP_PASS_MERCHBAY=merchbay_gmail_app_password
|
||||
```
|
||||
|
||||
### 2. Build and Run
|
||||
|
||||
```bash
|
||||
# Build image
|
||||
docker-compose build
|
||||
|
||||
# Start container
|
||||
docker-compose up -d
|
||||
|
||||
# Check logs
|
||||
docker logs -f email_reports_crew
|
||||
```
|
||||
|
||||
### 3. Test Manually
|
||||
|
||||
```bash
|
||||
# Test Crew report
|
||||
docker exec email_reports_unified BRAND=crew php /app/send_report.php
|
||||
|
||||
# Test MerchBay report
|
||||
docker exec email_reports_unified BRAND=merchbay php /app/send_report.php
|
||||
|
||||
# Check cron log
|
||||
docker exec email_reports_unified tail -f /var/log/cron.log
|
||||
|
||||
# Check email log (shows both brands)
|
||||
docker exec email_reports_unified tail -f /app/email.log
|
||||
```
|
||||
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Email Reports Container (Unified) │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────┐ │
|
||||
│ │ Cron Jobs: │ │
|
||||
│ │ 23:55 → BRAND=crew │ │
|
||||
│ │ 23:56 → BRAND=merchbay │ │
|
||||
│ │ │ │
|
||||
│ │ send_report.php │ │
|
||||
│ │ └─ PHPMailer │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└─────────────┼────────────────────────────┘
|
||||
│
|
||||
├──> MySQL: custom_design (Crew)
|
||||
├──> MySQL: merchbay_laravel (MerchBay)
|
||||
└──> SMTP: Gmail (2 accounts)
|
||||
│
|
||||
├──────> MySQL (crew-app-net)
|
||||
└──────> SMTP (smtp.gmail.com)
|
||||
```
|
||||
|
||||
## Fsend_report.php** - Unified report generator (handles both brands)
|
||||
- **index.php** - Legacy Crew report (kept for backward compatibility)
|
||||
- **dbconfig.php** - Database connection (env-aware)
|
||||
- **phpmailer/** - Email library
|
||||
|
||||
## Cron Schedule
|
||||
|
||||
```cron
|
||||
55 23 * * * BRAND=crew php /app/send_report.php >> /var/log/cron.log 2>&1
|
||||
56 23 * * * BRAND=merchbay php /app/send_report.php >> /var/log/cron.log 2>&1
|
||||
```
|
||||
|
||||
**23:55 CT** - Crew Sportswear
|
||||
**23:56 CT** - MerchBay
|
||||
|
||||
Both run daily, 1 minute apart.
|
||||
55 23 * * * cd /app && php index.php >> /var/log/cron.log 2>&1
|
||||
```
|
||||
|
||||
Runs daily at **11:55 PM Central Time**
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Crew Sportswear
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| DB_HOST_CREW | mysql | MySQL host for Crew |
|
||||
| DB_PORT_CREW | 3306 | MySQL port |
|
||||
| DB_NAME_CREW | custom_design | Crew database name |
|
||||
| DB_USER_CREW | crew_user | Crew database username |
|
||||
| DB_PASS_CREW | - | Crew database password |
|
||||
| SMTP_PASS_CREW | - | Gmail app password for mail@crewsportswear.com |
|
||||
|
||||
### MerchBay
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| DB_HOST_MERCHBAY | mysql | MySQL host for MerchBay |
|
||||
| DB_PORT_MERCHBAY | 3306 | MySQL port |
|
||||
| DB_NAME_MERCHBAY | merchbay_laravel | MerchBay database name |
|
||||
| DB_USER_MERCHBAY | merchbay_user | MerchBay database username |
|
||||
| DB_PASS_MERCHBAY | - | MerchBay database password |
|
||||
| SMTP_PASS_MERCHBAY | - | Gmail app password for support@merchbay.com |
|
||||
|
||||
### Shared
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| SMTP_HOST | smtp.gmail.com | SMTP server |
|
||||
| SMTP_PORT | 587 | SMTP port |
|
||||
|
||||
## Brand Configuration
|
||||
|
||||
The unified container handles both brands using the `BRAND` environment variable:
|
||||
|
||||
**Crew Sportswear (`BRAND=crew`)**:
|
||||
- Database: `custom_design`
|
||||
- From: `orders@crewsportswear.com`
|
||||
- To: `graphics@crewsportswear.com`
|
||||
- BCC: webmaster, angelo, production @crewsportswear.com
|
||||
- Admin: https://admin.crewsportswear.app
|
||||
- CSV: `daily_order_reports_crew/`
|
||||
|
||||
**MerchBay (`BRAND=merchbay`)**:
|
||||
- Database: `merchbay_laravel`
|
||||
- From: `orders@merchbay.com`
|
||||
- To: `graphics@crewsportswear.com`
|
||||
- BCC: webmaster, production @crewsportswear.com
|
||||
- Admin: https://merchbay.app
|
||||
- CSV: `daily_order_reports_merchbay/`
|
||||
|
||||
## Networks
|
||||
|
||||
Container must be on **crew-app-net** to access MySQL:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
- crew-app-net
|
||||
|
||||
networks:
|
||||
crew-app-net:
|
||||
external: true
|
||||
```
|
||||
|
||||
Create network if it doesn't exist:
|
||||
```bash
|
||||
docker network create crew-app-net
|
||||
```
|
||||
|
||||
## Volumes
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./daily_order_reports_crew:/app/daily_order_reports_crew # Crew CSV archives
|
||||
- ./daily_order_reports_merchbay:/app/daily_order_reports_merchbay # MerchBay CSV archives
|
||||
- ./email.log:/app/email.log # Combined execution log
|
||||
```
|
||||
|
||||
Both brands write to the same `email.log` with prefixes `[CREW]` and `[MERCHBAY]`.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Automated Deployment (Gitea Actions)
|
||||
|
||||
**Production deployment** is automated via Gitea Actions when pushing to `main` or `master` branch.
|
||||
|
||||
```bash
|
||||
# Trigger deployment
|
||||
git add .
|
||||
git commit -m "Update email reports"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
The workflow will:
|
||||
1. Build Docker image
|
||||
2. Transfer to production server via SSH
|
||||
3. Deploy to `/var/www/apps/email_reports`
|
||||
4. Start container with cron
|
||||
5. Verify container is running
|
||||
|
||||
**Registry build** is triggered by version tags:
|
||||
|
||||
```bash
|
||||
# Create version tag
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
This builds and pushes to your Docker registry.
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
```bash
|
||||
# Deploy via docker-compose
|
||||
docker-compose up -d --build
|
||||
|
||||
# Or build specific image version
|
||||
docker build -t email-reports:1.0.0 .
|
||||
docker tag email-reports:1.0.0 your-registry/email-reports:1.0.0
|
||||
docker push your-registry/email-reports:1.0.0
|
||||
```
|
||||
|
||||
### Required Gitea Secrets
|
||||
|
||||
Configure in your repository settings:
|
||||
|
||||
- `DEPLOY_SSH_KEY` - SSH private key for deployment server
|
||||
- `DEPLOY_HOST` - Deployment server hostname/IP
|
||||
- `DEPLOY_USER` - SSH username
|
||||
- `REGISTRY_URL` - Docker registry URL (for version tags)
|
||||
- `REGISTRY_USER` - Registry username
|
||||
- `REGISTRY_PASSWORD` - Registry password
|
||||
|
||||
### Server Setup (First Time)
|
||||
|
||||
On your production server, create the deployment directory and `.env` file:
|
||||
|
||||
```bash
|
||||
# Create directory
|
||||
sudo mkdir -p /var/www/apps/email_reports
|
||||
sudo chown $USER:$USER /var/www/apps/email_reports
|
||||
cd /var/www/apps/email_reports
|
||||
|
||||
# Create .env file
|
||||
cat > .env << 'EOL'
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_NAME=custom_design
|
||||
DB_USER=crew_user
|
||||
DB_PASS=your_secure_password
|
||||
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=mail@crewsportswear.com
|
||||
SMTP_PASS=your_gmail_app_password
|
||||
|
||||
EMAIL_TO=graphics@crewsportswear.com
|
||||
EMAIL_BCC=webmaster@crewsportswear.com,angelo@crewsportswear.com,production@crewsportswear.com
|
||||
|
||||
APP_URL=https://www.crewsportswear.com
|
||||
ADMIN_URL=https://admin.crewsportswear.app
|
||||
EOL
|
||||
|
||||
chmod 600 .env
|
||||
|
||||
# Create data directories
|
||||
mkdir -p daily_order_reports
|
||||
touch email.log
|
||||
chmod 666 email.log
|
||||
|
||||
# Ensure network exists
|
||||
docker network create crew-app-net || true
|
||||
```
|
||||
|
||||
Now push to trigger the first deployment!
|
||||
|
||||
### Multiple Instances
|
||||
|
||||
For MerchBay, create similar setup in `email_reports_merchbay/`:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml in email_reports_merchbay/
|
||||
services:
|
||||
email-reports-merchbay:
|
||||
build: .
|
||||
container_name: email_reports_merchbay
|
||||
environment:
|
||||
- DB_NAME=merchbay_db
|
||||
- EMAIL_TO=orders@merchbay.com
|
||||
# ... other env vars
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Check Container Health
|
||||
|
||||
```bash
|
||||
# Container status
|
||||
docker ps | grep email_reports
|
||||
|
||||
# Should show email_reports_unified
|
||||
|
||||
# Health check
|
||||
docker inspect email_reports_unified | grep -A 5 Health
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# Real-time cron logs (both brands)
|
||||
docker exec email_reports_unified tail -f /var/log/cron.log
|
||||
|
||||
# Email execution logs (prefixed by brand)
|
||||
docker exec email_reports_unified tail -f /app/email.log
|
||||
|
||||
# Example log output:
|
||||
# 2026-01-02 23:55:00 [CREW] successfully sent (5 orders)
|
||||
# 2026-01-02 23:56:00 [MERCHBAY] No order for today
|
||||
|
||||
# Last 50 lines
|
||||
docker logs --tail 50 email_reports_unified
|
||||
|
||||
# Filter by brand
|
||||
docker exec email_reports_unified grep "\[CREW\]" /app/email.log | tail -10
|
||||
docker exec email_reports_unified grep "\[MERCHBAY\]" /app/email.log | tail -10
|
||||
```
|
||||
|
||||
### Verify Cron Schedule
|
||||
|
||||
```bash
|
||||
# View active crontab (should show both jobs)
|
||||
docker exec email_reports_unified crontab -l
|
||||
|
||||
# Expected:
|
||||
# 55 23 * * * BRAND=crew php /app/send_report.php >> /var/log/cron.log 2>&1
|
||||
# 56 23 * * * BRAND=merchbay php /app/send_report.php >> /var/log/cron.log 2>&1
|
||||
|
||||
# Check if cron is running
|
||||
docker exec email_reports_unified ps aux | grep crond
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Test MySQL connectivity
|
||||
docker exec email_reports_crew ping mysql
|
||||
|
||||
# Test database connection
|
||||
docker exec email_reports_crew php -r "new PDO('mysql:host=mysql;dbname=custom_design', 'user', 'pass');"
|
||||
|
||||
# Check network
|
||||
docker network inspect crew-app-net
|
||||
```
|
||||
|
||||
### Email Not Sending
|
||||
|
||||
```bash
|
||||
# Enable debug mode
|
||||
docker exec email_reports_crew sed -i 's|// \$mail->SMTPDebug|\$mail->SMTPDebug|' /app/index.php
|
||||
|
||||
# Run manually with debug
|
||||
docker exec email_reports_crew php /app/index.php
|
||||
|
||||
# Check SMTP credentials in .env
|
||||
```
|
||||
|
||||
### Cron Not Running
|
||||
|
||||
```bash
|
||||
# Check cron daemon
|
||||
docker exec email_reports_crew ps aux | grep crond
|
||||
|
||||
# Restart container
|
||||
docker restart email_reports_crew
|
||||
|
||||
# View crontab
|
||||
docker exec email_reports_crew cat /etc/crontabs/root
|
||||
```
|
||||
|
||||
### Wrong Timezone
|
||||
|
||||
```bash
|
||||
# Verify timezone
|
||||
docker exec email_reports_crew date
|
||||
docker exec email_reports_crew cat /etc/timezone
|
||||
|
||||
# Should show: America/Chicago
|
||||
```
|
||||
|
||||
### Permissions Issues
|
||||
|
||||
```bash
|
||||
# Fix CSV directory permissions
|
||||
docker exec email_reports_crew chmod 755 /app/daily_order_reports
|
||||
|
||||
# Fix log file
|
||||
docker exec email_reports_crew chmod 666 /app/email.log
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Update Cron Schedule
|
||||
|
||||
Edit Dockerfile and rebuild:
|
||||
```dockerfile
|
||||
RUN echo "30 22 * * * cd /app && php index.php" > /etc/crontabs/root
|
||||
```
|
||||
|
||||
### Rotate Logs
|
||||
|
||||
```bash
|
||||
# Manually clear logs (optional)
|
||||
docker exec email_reports_crew truncate -s 0 /var/log/cron.log
|
||||
docker exec email_reports_crew truncate -s 0 /app/email.log
|
||||
```
|
||||
|
||||
### Backup Reports
|
||||
|
||||
```bash
|
||||
# Archive old CSV files
|
||||
tar -czf daily_reports_backup_$(date +%Y%m%d).tar.gz daily_order_reports/
|
||||
|
||||
# Or sync to S3/backup location
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
1. **Never commit `.env`** - Use `.env.example` only
|
||||
2. **Use Gmail App Passwords** - Not your main password
|
||||
3. **Restrict network access** - Only join necessary networks
|
||||
4. **Rotate credentials** - Change passwords regularly
|
||||
5. **Monitor logs** - Check for unauthorized access
|
||||
|
||||
## Advantages of Separate Container
|
||||
|
||||
✅ **Isolation** - Reports run independently of web apps
|
||||
✅ **Reusability** - Can serve multiple apps (Crew + MerchBay)
|
||||
✅ **Simple Deployment** - Single purpose, easy to update
|
||||
✅ **Resource Control** - Limit CPU/memory for cron jobs
|
||||
✅ **Independent Scaling** - Restart without affecting web traffic
|
||||
✅ **Clean Separation** - No coupling with Laravel/framework
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Test database connectivity
|
||||
2. Verify SMTP credentials
|
||||
3. Run manual test: `docker exec email_reports_crew php /app/index.php`
|
||||
4. Wait for 23:55 or adjust cron for testing
|
||||
5. Monitor logs for first automated run
|
||||
6. Set up similar container for MerchBay reports
|
||||
@@ -1,22 +0,0 @@
|
||||
StoreName,ProductName,NAME,NUMBER,Size,JerseySize,ShortsSize,Price,Quantity,TotalPrice,Tax,DateCreated,InvoiceNumber,Payer_Email
|
||||
"World Class Basketball",Socks,,,,,,12.00,2,24,2.4000000000000004,"2019-03-13 14:11:35",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Sleeveless Hoodie",,,M,,,25.00,1,25,2.5,"2019-03-13 14:14:14",2019-5c8958991de3a,
|
||||
"World Class Basketball","Wisconsin Long Sleeve",,,L,,,25.00,1,25,2.5,"2019-03-13 14:15:48",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,44,,YXL,YXL,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,3,,YXL,YXL,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,15,,M,M,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,11,,S,S,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,0,,YL,YL,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,2,,S,S,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,5,,YXL,YXL,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,20,,YL,YL,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Away Uniform",,4,,YL,YL,40.00,1,40,4,"2019-03-13 14:18:57",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,15,,M,M,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,11,,S,S,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,0,,YL,YL,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,2,,S,S,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,5,,YXL,YXL,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,20,,YL,YL,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,4,,YL,YL,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,44,,YXL,YXL,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
"World Class Basketball","Illinois Home Uniform",,3,,YXL,YXL,40.00,1,40,4,"2019-03-13 14:22:29",2019-5c8958991de3a,
|
||||
|
@@ -1,180 +0,0 @@
|
||||
StoreName,ProductName,NAME,NUMBER,Size,JerseySize,ShortsSize,Price,Quantity,TotalPrice,Tax,DateCreated,InvoiceNumber,Payer_Email
|
||||
Limitless,"Black Uniform",,12,,S,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,11,,YXL,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,6,,S,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,30,,YXL,YXL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,30,,S,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,15,,YXL,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,25,,S,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,1,,YXS,YS,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,8,,M,YXL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,18,,YM,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,0,,M,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,6,,YL,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,23,,M,XL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,17,,YL,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,1,,L,YXL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,7,,YL,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,13,,YL,YM,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,0,,S,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,4,,S,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,21,,YXL,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,0,,YXL,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,34,,S,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,10,,YXL,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,67,,S,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,3,,YXL,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,11,,M,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,29,,YXS,YM,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,7,,M,L,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,23,,YM,YS,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,7,,M,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,53,,YL,YXL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,10,,M,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,21,,YL,L,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,24,,L,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,2,,YL,L,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,27,,XL,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,23,,S,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,21,,S,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,23,,YXL,YXL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,33,,YXL,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,8,,S,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,11,,YXL,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,54,,S,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,7,,YXL,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,9,,M,YM,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,5,,YM,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,34,,M,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,10,,YM,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,2,,M,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,17,,YL,YL,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,0,,L,YS,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,0,,YL,M,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,19,,YL,YM,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Black Uniform",,34,,XL,S,45.00,1,45,4.5,"2019-08-31 22:22:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,22,L,,,25.00,1,25,2.5,"2019-08-31 22:27:51",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,40,L,,,25.00,1,25,2.5,"2019-08-31 22:27:51",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,34,L,,,25.00,1,25,2.5,"2019-08-31 22:28:24",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,5,L,,,25.00,1,25,2.5,"2019-08-31 22:28:24",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,1,L,,,25.00,1,25,2.5,"2019-08-31 22:29:26",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,24,L,,,25.00,1,25,2.5,"2019-08-31 22:29:26",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,5,M,,,25.00,1,25,2.5,"2019-08-31 22:33:16",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,L,,,25.00,1,25,2.5,"2019-08-31 22:33:16",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,42,M,,,25.00,1,25,2.5,"2019-08-31 22:35:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,35,M,,,25.00,1,25,2.5,"2019-08-31 22:35:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,M,,,25.00,1,25,2.5,"2019-08-31 22:35:44",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,M,,,25.00,1,25,2.5,"2019-08-31 22:35:44",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,12,M,,,25.00,1,25,2.5,"2019-08-31 22:36:13",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,9,M,,,25.00,1,25,2.5,"2019-08-31 22:36:13",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,4,M,,,25.00,1,25,2.5,"2019-08-31 22:36:29",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,4,M,,,25.00,1,25,2.5,"2019-08-31 22:36:29",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,10,M,,,25.00,1,25,2.5,"2019-08-31 22:40:17",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,42,M,,,25.00,1,25,2.5,"2019-08-31 22:40:17",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,2,M,,,25.00,1,25,2.5,"2019-08-31 22:40:56",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,M,,,25.00,1,25,2.5,"2019-08-31 22:40:56",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,7,M,,,25.00,1,25,2.5,"2019-08-31 22:42:48",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,M,,,25.00,1,25,2.5,"2019-08-31 22:42:48",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,7,M,,,25.00,1,25,2.5,"2019-08-31 22:44:29",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,34,M,,,25.00,1,25,2.5,"2019-08-31 22:44:29",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,9,M,,,25.00,1,25,2.5,"2019-08-31 22:45:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,8,M,,,25.00,1,25,2.5,"2019-08-31 22:45:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,11,M,,,25.00,1,25,2.5,"2019-08-31 22:45:59",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,35,S,,,25.00,1,25,2.5,"2019-08-31 22:45:59",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,33,S,,,25.00,1,25,2.5,"2019-08-31 22:47:01",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,3,S,,,25.00,1,25,2.5,"2019-08-31 22:47:01",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,S,,,25.00,1,25,2.5,"2019-08-31 22:47:29",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,8,S,,,25.00,1,25,2.5,"2019-08-31 22:47:29",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,15,S,,,25.00,1,25,2.5,"2019-08-31 22:48:30",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,S,,,25.00,1,25,2.5,"2019-08-31 22:48:30",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,20,S,,,25.00,1,25,2.5,"2019-08-31 22:49:02",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,1,S,,,25.00,1,25,2.5,"2019-08-31 22:49:02",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,12,S,,,25.00,1,25,2.5,"2019-08-31 22:49:42",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,31,S,,,25.00,1,25,2.5,"2019-08-31 22:49:42",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,11,S,,,25.00,1,25,2.5,"2019-08-31 22:50:16",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,9,S,,,25.00,1,25,2.5,"2019-08-31 22:50:16",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,5,S,,,25.00,1,25,2.5,"2019-08-31 22:51:24",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,1,S,,,25.00,1,25,2.5,"2019-08-31 22:51:24",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,35,S,,,25.00,1,25,2.5,"2019-08-31 22:51:59",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,1,S,,,25.00,1,25,2.5,"2019-08-31 22:51:59",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,14,S,,,25.00,1,25,2.5,"2019-08-31 22:52:28",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,S,,,25.00,1,25,2.5,"2019-08-31 22:52:28",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,12,S,,,25.00,1,25,2.5,"2019-08-31 22:53:20",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,25,S,,,25.00,1,25,2.5,"2019-08-31 22:53:20",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,67,S,,,25.00,1,25,2.5,"2019-08-31 22:56:45",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,54,S,,,25.00,1,25,2.5,"2019-08-31 22:56:45",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,8,S,,,25.00,1,25,2.5,"2019-08-31 22:58:19",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,30,S,,,25.00,1,25,2.5,"2019-08-31 22:58:19",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,34,S,,,25.00,1,25,2.5,"2019-08-31 23:09:10",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,6,S,,,25.00,1,25,2.5,"2019-08-31 23:09:10",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,4,S,,,25.00,1,25,2.5,"2019-08-31 23:09:44",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,S,,,25.00,1,25,2.5,"2019-08-31 23:09:44",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,S,,,25.00,1,25,2.5,"2019-08-31 23:11:18",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,12,S,,,25.00,1,25,2.5,"2019-08-31 23:11:18",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,S,,,25.00,1,25,2.5,"2019-08-31 23:11:57",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,26,XL,,,25.00,1,25,2.5,"2019-08-31 23:11:57",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,34,XL,,,25.00,1,25,2.5,"2019-08-31 23:13:25",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,2,XL,,,25.00,1,25,2.5,"2019-08-31 23:13:25",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,11,XL,,,25.00,1,25,2.5,"2019-08-31 23:13:55",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,20,XL,,,25.00,1,25,2.5,"2019-08-31 23:13:55",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,34,XL,,,25.00,1,25,2.5,"2019-08-31 23:15:34",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,27,XL,,,25.00,1,25,2.5,"2019-08-31 23:15:34",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,15,YL,,,25.00,1,25,2.5,"2019-08-31 23:16:17",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,3,YL,,,25.00,1,25,2.5,"2019-08-31 23:16:17",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,28,YL,,,25.00,1,25,2.5,"2019-08-31 23:16:59",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,YL,,,25.00,1,25,2.5,"2019-08-31 23:16:59",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,2,YL,,,25.00,1,25,2.5,"2019-08-31 23:18:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,36,YL,,,25.00,1,25,2.5,"2019-08-31 23:18:38",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,13,YL,,,25.00,1,25,2.5,"2019-08-31 23:19:09",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,22,YL,,,25.00,1,25,2.5,"2019-08-31 23:19:09",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,22,YL,,,25.00,1,25,2.5,"2019-08-31 23:20:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,10,YL,,,25.00,1,25,2.5,"2019-08-31 23:20:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,33,YL,,,25.00,1,25,2.5,"2019-08-31 23:20:57",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,30,YL,,,25.00,1,25,2.5,"2019-08-31 23:20:57",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,30,YL,,,25.00,1,25,2.5,"2019-08-31 23:21:28",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,33,YL,,,25.00,1,25,2.5,"2019-08-31 23:21:28",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,YL,,,25.00,1,25,2.5,"2019-08-31 23:22:05",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,30,YL,,,25.00,1,25,2.5,"2019-08-31 23:22:05",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,19,YL,,,25.00,1,25,2.5,"2019-08-31 23:22:43",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,13,YL,,,25.00,1,25,2.5,"2019-08-31 23:22:43",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,2,YL,,,25.00,1,25,2.5,"2019-08-31 23:23:07",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,7,YL,,,25.00,1,25,2.5,"2019-08-31 23:23:07",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,YL,,,25.00,1,25,2.5,"2019-08-31 23:24:00",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,YL,,,25.00,1,25,2.5,"2019-08-31 23:24:00",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,17,YL,,,25.00,1,25,2.5,"2019-08-31 23:24:33",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,17,YL,,,25.00,1,25,2.5,"2019-08-31 23:24:33",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,53,YL,,,25.00,1,25,2.5,"2019-08-31 23:25:07",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,6,YL,,,25.00,1,25,2.5,"2019-08-31 23:25:07",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,5,YM,,,25.00,1,25,2.5,"2019-08-31 23:25:33",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,3,YM,,,25.00,1,25,2.5,"2019-08-31 23:25:33",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,33,YM,,,25.00,1,25,2.5,"2019-08-31 23:26:05",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,YM,,,25.00,1,25,2.5,"2019-08-31 23:26:05",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,8,YM,,,25.00,1,25,2.5,"2019-08-31 23:26:49",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,34,YM,,,25.00,1,25,2.5,"2019-08-31 23:26:49",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,YM,,,25.00,1,25,2.5,"2019-08-31 23:27:19",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,2,YM,,,25.00,1,25,2.5,"2019-08-31 23:27:19",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,10,YM,,,25.00,1,25,2.5,"2019-08-31 23:27:51",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,28,YM,,,25.00,1,25,2.5,"2019-08-31 23:27:51",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,18,YM,,,25.00,1,25,2.5,"2019-08-31 23:29:05",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,YM,,,25.00,1,25,2.5,"2019-08-31 23:29:05",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,5,YM,,,25.00,1,25,2.5,"2019-08-31 23:29:36",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,29,YS,,,25.00,1,25,2.5,"2019-08-31 23:31:02",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,5,YS,,,25.00,1,25,2.5,"2019-08-31 23:31:19",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,1,YS,,,25.00,1,25,2.5,"2019-08-31 23:31:47",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,YXL,,,25.00,1,25,2.5,"2019-08-31 23:32:15",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,33,YXL,,,25.00,1,25,2.5,"2019-08-31 23:32:15",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,11,YXL,,,25.00,1,25,2.5,"2019-08-31 23:32:45",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,10,YXL,,,25.00,1,25,2.5,"2019-08-31 23:32:45",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,9,YXL,,,25.00,1,25,2.5,"2019-08-31 23:33:24",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,20,YXL,,,25.00,1,25,2.5,"2019-08-31 23:33:24",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,3,YXL,,,25.00,1,25,2.5,"2019-08-31 23:34:02",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,7,YXL,,,25.00,1,25,2.5,"2019-08-31 23:34:02",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,15,YXL,,,25.00,1,25,2.5,"2019-08-31 23:34:28",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,11,YXL,,,25.00,1,25,2.5,"2019-08-31 23:34:28",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,30,YXL,,,25.00,1,25,2.5,"2019-08-31 23:34:55",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,10,YXL,,,25.00,1,25,2.5,"2019-08-31 23:34:55",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,0,YXL,,,25.00,1,25,2.5,"2019-08-31 23:35:27",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,33,YXL,,,25.00,1,25,2.5,"2019-08-31 23:35:27",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,11,YXL,,,25.00,1,25,2.5,"2019-08-31 23:35:56",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,23,YXL,,,25.00,1,25,2.5,"2019-08-31 23:35:56",20190901-0181,dawaun@limitlessbasketball.com
|
||||
Limitless,"Reversible Jersey",,21,YXL,,,25.00,1,25,2.5,"2019-08-31 23:36:11",20190901-0181,dawaun@limitlessbasketball.com
|
||||
|
@@ -1,4 +0,0 @@
|
||||
StoreName,ProductName,NAME,NUMBER,Size,JerseySize,ShortsSize,Price,Quantity,TotalPrice,Tax,DateCreated,InvoiceNumber,Payer_Email
|
||||
"Bruton Basketball","Long Sleeve T Shirt",,,2XL,,,45.00,1,45,4.5,"2019-09-01 17:38:41",20190901-0182,clare.davis@fire.tas.gov.au
|
||||
"Bruton Basketball","T Shirt",,,2XL,,,35.00,1,35,3.5,"2019-09-01 17:39:01",20190901-0182,clare.davis@fire.tas.gov.au
|
||||
"Bruton Basketball","Long Sleeve Hoodie",,,3XL,,,50.00,1,50,5,"2019-09-01 17:39:19",20190901-0182,clare.davis@fire.tas.gov.au
|
||||
|
@@ -1,7 +0,0 @@
|
||||
StoreName,ProductName,NAME,NUMBER,Size,JerseySize,ShortsSize,Price,Quantity,TotalPrice,Tax,DateCreated,InvoiceNumber,Payer_Email
|
||||
Ice,"Practice Jersey Reversible",SIMON,55,YM,,,30.00,1,30,3,"2019-10-26 09:17:16",20191026-0275,simondj55@gmail.com
|
||||
"San Diego Bulldogs","Long Sleeve Hoodie",,,YXL,,,45.00,1,45,4.5,"2019-10-26 17:50:31",20191026-0276,sjones023@att.net
|
||||
"San Diego Bulldogs",Backpack,"MJ ",23,,,,70.00,1,70,7,"2019-10-26 17:51:14",20191026-0276,sjones023@att.net
|
||||
Wauconda,"Game Day Reversible uniform",PIEHL,12,,L,L,65.00,1,65,6.5,"2019-10-26 18:12:46",20191026-0277,aprilo99@aol.com
|
||||
Wauconda,"Dry fit Long Sleeve T Shirt",,,L,,,30.00,1,30,3,"2019-10-26 18:21:16",20191026-0277,aprilo99@aol.com
|
||||
Wauconda,socks,,,,,,12.00,1,12,1.2000000000000002,"2019-10-26 18:21:33",20191026-0277,aprilo99@aol.com
|
||||
|
@@ -48,372 +48,13 @@ $array_orders = getSubItem($dateToday);
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Order Details</title>
|
||||
<style>
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 880px;
|
||||
padding: 10px;
|
||||
width: 880px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 880px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn>tbody>tr>td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.table-bordered td,
|
||||
th {
|
||||
border: 1px solid black;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.previewImage {
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.items tr {
|
||||
page-break-inside: avoid;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@page {
|
||||
size: 18cm 26.7cm;
|
||||
margin: 1cm;
|
||||
}
|
||||
|
||||
#tbl_subitem tr td {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body class="" onload="window.print()">
|
||||
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
@@ -426,8 +67,7 @@ $array_orders = getSubItem($dateToday);
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="align-center" style="font-size: 15px;"><b>Daily Reports
|
||||
<?php echo $dateToday ?></b> </h3>
|
||||
<h3 class="align-center" style="font-size: 15px;"><b>Daily Reports <?php echo $dateToday ?></b></h3>
|
||||
<br>
|
||||
<?php
|
||||
$q = $conn->prepare("SELECT t.StoreName, o.ProductId, o.FormUsed, o.ProductName, o.NAME, o.NUMBER, o.Size, o.JerseySize, o.ShortsSize, o.Price, o.Quantity, (o.Price * o.Quantity) AS TotalPrice, ((o.Price * o.Quantity) * 0.10) AS Tax,
|
||||
@@ -441,15 +81,10 @@ $array_orders = getSubItem($dateToday);
|
||||
$result = $q->rowCount();
|
||||
|
||||
if($result > 0){
|
||||
// $i = 1;
|
||||
while ($row = $q->fetch()) {
|
||||
|
||||
?>
|
||||
<div class="items-parent"
|
||||
style="border: 1px solid #e2e2e2; padding: 10px; margin-bottom: 10px;">
|
||||
|
||||
<table class="items" role="presentation" border="0" cellpadding="0"
|
||||
cellspacing="0">
|
||||
<div class="items-parent" style="border: 1px solid #e2e2e2; padding: 10px; margin-bottom: 10px;">
|
||||
<table class="items" role="presentation" border="0" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
@@ -462,27 +97,18 @@ $array_orders = getSubItem($dateToday);
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div style="font-size: 12px;">
|
||||
<a href="#">
|
||||
<?php echo $row['ProductName'] ?>
|
||||
</a>
|
||||
<a href="#"><?php echo $row['ProductName'] ?></a>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<!-- -->
|
||||
<?php
|
||||
$itemImages = getItemImages($row['ProductId']);
|
||||
foreach($itemImages as $thumb_key => $thumb_val){
|
||||
// echo $thumb_val['Image'];
|
||||
echo '<img style="height: 150px; overflow: hidden; object-fit: contain;" src="https://crewsportswear.app/images/'.$thumb_val['Image'].'"> ';
|
||||
echo '<img style="height: 150px; overflow: hidden; object-fit: contain;" src="https://crewsportswear.app:5955/'.$thumb_val['Image'].'"> ';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<table id="tbl_subitem"
|
||||
class="table table-condensed table-bordered"
|
||||
style="width: 100%; border-collapse: collapse;">
|
||||
<?php
|
||||
|
||||
if($row['FormUsed']=="jersey-and-shorts-form"){
|
||||
?>
|
||||
<table id="tbl_subitem"class="table table-condensed table-bordered" style="width: 100%; border-collapse: collapse;">
|
||||
<?php if($row['FormUsed']=="jersey-and-shorts-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<td><b>Name</b></td>
|
||||
@@ -491,33 +117,25 @@ $array_orders = getSubItem($dateToday);
|
||||
<td><b>Shorts Size</b></td>
|
||||
<td><b>Quantity</b></td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="tshirt-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="tshirt-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<td><b>Size</b></td>
|
||||
<td><b>Quantity</b></td>
|
||||
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="quantity-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="quantity-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<td>Quantity</b></td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="name-number-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="name-number-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<td><b>Name</b></td>
|
||||
<td><b>Number</b></td>
|
||||
<td><b>Quantity</b></td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="name-number-size-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="name-number-size-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<th>Name</th>
|
||||
@@ -525,78 +143,76 @@ $array_orders = getSubItem($dateToday);
|
||||
<th>Size</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="number-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="number-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<th>Number</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="name-size-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="name-name2-size-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<td>Gamer Tag</td>
|
||||
<td>Name</td>
|
||||
<td>Size</td>
|
||||
<td>Quantity</td>
|
||||
</tr>
|
||||
<?php }elseif($row['FormUsed']=="name-size-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Price</th>
|
||||
<th>Quantity</th>
|
||||
|
||||
</tr>
|
||||
<?php
|
||||
|
||||
}else{
|
||||
// else
|
||||
}
|
||||
<?php }elseif($row['FormUsed']=="jersey-and-shorts-quantity-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<th>Jersey Size</th>
|
||||
<th>Shorts Size</th>
|
||||
<th>Quantity</th>
|
||||
</tr>
|
||||
<?php }elseif($row['FormUsed']=="number-jersey-shorts-form"){ ?>
|
||||
<tr>
|
||||
<td><b>#</b></td>
|
||||
<td><b>Number</b></td>
|
||||
<td><b>Jersey Size</b></td>
|
||||
<td><b>Shorts Size</b></td>
|
||||
<td><b>Quantity</b></td>
|
||||
</tr>
|
||||
<?php }else{ /* else */ }
|
||||
|
||||
$i=1;
|
||||
|
||||
foreach($array_orders as $key => $val){
|
||||
if($val['ProductId'] == $row['ProductId']){
|
||||
|
||||
if($row['FormUsed']=="jersey-and-shorts-form"){
|
||||
?>
|
||||
if($row['FormUsed']=="jersey-and-shorts-form"){ ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php echo $i++ ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo ($val['Name'] !== "") ? : '--' ?>
|
||||
</td>
|
||||
<td><?php echo $i++ ?></td>
|
||||
<td><?php echo ($val['Name'] !== "") ? : '--' ?></td>
|
||||
<td><?php echo $val['Number'] ?> </td>
|
||||
<td><?php echo $val['JerseySize'] ?> </td>
|
||||
<td><?php echo $val['ShortsSize'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="tshirt-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="tshirt-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Size'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="quantity-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="quantity-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="name-number-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="name-number-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Name'] ?> </td>
|
||||
<td><?php echo $val['Number'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="name-number-size-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="name-number-size-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Name'] ?> </td>
|
||||
@@ -604,35 +220,45 @@ $array_orders = getSubItem($dateToday);
|
||||
<td><?php echo $val['Size'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="number-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="number-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Number'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php
|
||||
}elseif($row['FormUsed']=="name-size-form"){
|
||||
?>
|
||||
<?php }elseif($row['FormUsed']=="name-name2-size-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Name'] ?> </td>
|
||||
<td><?php echo $val['Name2'] ?> </td>
|
||||
<td><?php echo $val['Size'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php }elseif($row['FormUsed']=="name-size-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['Name'] ?> </td>
|
||||
<td><?php echo $val['Size'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php
|
||||
}else{
|
||||
|
||||
<?php }elseif($row['FormUsed']=="jersey-and-shorts-quantity-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?> </td>
|
||||
<td><?php echo $val['JerseySize'] ?> </td>
|
||||
<td><?php echo $val['ShortsSize'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php }elseif($row['FormUsed']=="number-jersey-shorts-form"){ ?>
|
||||
<tr>
|
||||
<td><?php echo $i++ ?></td>
|
||||
<td><?php echo $val['Number'] ?> </td>
|
||||
<td><?php echo $val['JerseySize'] ?> </td>
|
||||
<td><?php echo $val['ShortsSize'] ?> </td>
|
||||
<td><?php echo $val['Quantity'] ?> </td>
|
||||
</tr>
|
||||
<?php }else{ /* else */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
<!-- table header -->
|
||||
<!-- table body -->
|
||||
|
||||
} ?>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -643,8 +269,6 @@ $array_orders = getSubItem($dateToday);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -671,7 +295,7 @@ $array_orders = getSubItem($dateToday);
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
14
dbconfig.php
14
dbconfig.php
@@ -1,18 +1,18 @@
|
||||
<?php
|
||||
|
||||
$host = "localhost";
|
||||
$username = "root";
|
||||
$password = "";
|
||||
$db = "custom_design";
|
||||
// Use environment variables for Docker deployment
|
||||
$host = getenv('DB_HOST') ?: 'localhost';
|
||||
$username = getenv('DB_USER') ?: 'root';
|
||||
$password = getenv('DB_PASS') ?: '';
|
||||
$db = getenv('DB_NAME') ?: 'custom_designs';
|
||||
$port = getenv('DB_PORT') ?: '3306';
|
||||
|
||||
try{
|
||||
// database connection
|
||||
$conn = new PDO("mysql:host=$host;dbname=$db",$username,$password);
|
||||
$conn = new PDO("mysql:host=$host;port=$port;dbname=$db", $username, $password);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
catch(PDOException $pe)
|
||||
{
|
||||
die('Connection error, because: ' .$pe->getMessage());
|
||||
}
|
||||
|
||||
$baseURL = 'http://'.$_SERVER['HTTP_HOST'] . '/bulacanlibrary/';
|
||||
46
docker-compose.yml
Normal file
46
docker-compose.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
email-reports:
|
||||
build: .
|
||||
container_name: email_reports_unified
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
|
||||
# Crew Sportswear Database
|
||||
- DB_HOST_CREW=${DB_HOST_CREW:-mysql}
|
||||
- DB_PORT_CREW=${DB_PORT_CREW:-3306}
|
||||
- DB_NAME_CREW=${DB_NAME_CREW:-custom_design}
|
||||
- DB_USER_CREW=${DB_USER_CREW:-crew_user}
|
||||
- DB_PASS_CREW=${DB_PASS_CREW}
|
||||
|
||||
# MerchBay Database
|
||||
- DB_HOST_MERCHBAY=${DB_HOST_MERCHBAY:-mysql}
|
||||
- DB_PORT_MERCHBAY=${DB_PORT_MERCHBAY:-3306}
|
||||
- DB_NAME_MERCHBAY=${DB_NAME_MERCHBAY:-merchbay_laravel}
|
||||
- DB_USER_MERCHBAY=${DB_USER_MERCHBAY:-merchbay_user}
|
||||
- DB_PASS_MERCHBAY=${DB_PASS_MERCHBAY}
|
||||
|
||||
# SMTP Configuration (shared)
|
||||
- SMTP_HOST=${SMTP_HOST:-smtp.gmail.com}
|
||||
- SMTP_PORT=${SMTP_PORT:-587}
|
||||
|
||||
# Crew SMTP
|
||||
- SMTP_PASS_CREW=${SMTP_PASS_CREW}
|
||||
|
||||
# MerchBay SMTP
|
||||
- SMTP_PASS_MERCHBAY=${SMTP_PASS_MERCHBAY}
|
||||
|
||||
volumes:
|
||||
- ./daily_order_reports_crew:/app/daily_order_reports_crew
|
||||
- ./daily_order_reports_merchbay:/app/daily_order_reports_merchbay
|
||||
- ./email.log:/app/email.log
|
||||
networks:
|
||||
- crew-app-net
|
||||
labels:
|
||||
- "description=Unified Email Reports for Crew & MerchBay"
|
||||
|
||||
networks:
|
||||
crew-app-net:
|
||||
external: true
|
||||
27
index.php
27
index.php
@@ -4,6 +4,7 @@ include 'dbconfig.php';
|
||||
require("phpmailer/class.phpmailer.php");
|
||||
date_default_timezone_set('America/Chicago');
|
||||
$dateToday = date('Y-m-d');
|
||||
//$dateToday = "2019-12-12";
|
||||
$dateTimeToday = date("Y-m-d H:i:s");
|
||||
|
||||
//grab all rows from the table where state is "new" and put into array
|
||||
@@ -40,7 +41,7 @@ if($result > 0){
|
||||
|
||||
}
|
||||
|
||||
$report_link = "https://www.crewsportswear.com/email_reports/daily_reports_with_image.php?d=". $dateToday;
|
||||
$report_link = (getenv('APP_URL') ?: 'https://www.crewsportswear.com') . "/email_reports/daily_reports_with_image.php?d=". $dateToday;
|
||||
$body = "<html>";
|
||||
$body .= "<body>";
|
||||
$body .= "<p> Please download the attached file for today's order report.</p>";
|
||||
@@ -51,7 +52,8 @@ if($result > 0){
|
||||
|
||||
foreach($invoice_array as $invoice){
|
||||
$v = explode("|", $invoice);
|
||||
$body .= "<div>Invoice Number: <a href='http://admin.crewsportswear.com:23000/admin/orders/view/" . $v['0']."/print'>" . $v['1'] . "</a></div>";
|
||||
$admin_url = getenv('ADMIN_URL') ?: 'https://admin.crewsportswear.app';
|
||||
$body .= "<div>Invoice Number: <a href='" . $admin_url . "/admin/orders/view/" . $v['0']."/print'>" . $v['1'] . "</a></div>";
|
||||
}
|
||||
|
||||
$body .= "</body>";
|
||||
@@ -63,18 +65,23 @@ if($result > 0){
|
||||
// $mail->SMTPDebug = 3; // uncomment for debug mode.
|
||||
|
||||
$mail->SMTPSecure = 'tls';
|
||||
$mail->Host = "smtp.gmail.com";
|
||||
$mail->Port = "587";
|
||||
$mail->Host = getenv('SMTP_HOST') ?: 'smtp.gmail.com';
|
||||
$mail->Port = getenv('SMTP_PORT') ?: '587';
|
||||
|
||||
//gmail account
|
||||
$mail->Username = 'no-reply@crewsportswear.com';
|
||||
$mail->Password = '20HustleHard19!';
|
||||
//gmail account credentials from environment
|
||||
$mail->Username = getenv('SMTP_USER') ?: 'mail@crewsportswear.com';
|
||||
$mail->Password = getenv('SMTP_PASS') ?: 'tjpwykpttvkjilxh';
|
||||
|
||||
$mail->SetFrom('orders@crewsportswear.com', 'CREW Daily Order Report');
|
||||
|
||||
$mail->addAddress('graphics@crewsportswear.com');
|
||||
$mail->addBCC("webmaster@crewsportswear.com");
|
||||
$mail->addBCC("angelo@crewsportswear.com");
|
||||
// Recipients from environment or defaults
|
||||
$email_to = getenv('EMAIL_TO') ?: 'graphics@crewsportswear.com';
|
||||
$mail->addAddress($email_to);
|
||||
|
||||
$email_bcc = getenv('EMAIL_BCC') ?: 'webmaster@crewsportswear.com,angelo@crewsportswear.com,production@crewsportswear.com';
|
||||
foreach (explode(',', $email_bcc) as $bcc_email) {
|
||||
$mail->addBCC(trim($bcc_email));
|
||||
}
|
||||
|
||||
$mail->addAttachment('daily_order_reports/'.$filename);
|
||||
|
||||
|
||||
154
send_report.php
Normal file
154
send_report.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
include 'dbconfig.php';
|
||||
require("phpmailer/class.phpmailer.php");
|
||||
date_default_timezone_set('America/Chicago');
|
||||
$dateToday = date('Y-m-d');
|
||||
$dateTimeToday = date("Y-m-d H:i:s");
|
||||
|
||||
// Determine which brand to process
|
||||
$brand = getenv('BRAND') ?: 'crew'; // 'crew' or 'merchbay'
|
||||
|
||||
// Brand-specific configuration
|
||||
$config = [
|
||||
'crew' => [
|
||||
'db_name' => getenv('DB_NAME_CREW') ?: 'custom_design',
|
||||
'db_host' => getenv('DB_HOST_CREW') ?: getenv('DB_HOST') ?: 'mysql',
|
||||
'db_user' => getenv('DB_USER_CREW') ?: 'crew_user',
|
||||
'db_pass' => getenv('DB_PASS_CREW') ?: getenv('DB_PASS'),
|
||||
'smtp_user' => 'mail@crewsportswear.com',
|
||||
'smtp_pass' => getenv('SMTP_PASS_CREW') ?: getenv('SMTP_PASS'),
|
||||
'email_from' => 'orders@crewsportswear.com',
|
||||
'email_from_name' => 'CREW Daily Order Report',
|
||||
'email_to' => 'graphics@crewsportswear.com',
|
||||
'email_bcc' => ['webmaster@crewsportswear.com', 'angelo@crewsportswear.com', 'production@crewsportswear.com'],
|
||||
'report_url' => 'https://www.crewsportswear.com/email_reports/daily_reports_with_image.php',
|
||||
'admin_url' => 'https://admin.crewsportswear.app',
|
||||
'csv_dir' => 'daily_order_reports_crew',
|
||||
'log_prefix' => '[CREW]'
|
||||
],
|
||||
'merchbay' => [
|
||||
'db_name' => getenv('DB_NAME_MERCHBAY') ?: 'merchbay_laravel',
|
||||
'db_host' => getenv('DB_HOST_MERCHBAY') ?: getenv('DB_HOST') ?: 'mysql',
|
||||
'db_user' => getenv('DB_USER_MERCHBAY') ?: 'merchbay_user',
|
||||
'db_pass' => getenv('DB_PASS_MERCHBAY') ?: getenv('DB_PASS'),
|
||||
'smtp_user' => 'support@merchbay.com',
|
||||
'smtp_pass' => getenv('SMTP_PASS_MERCHBAY') ?: getenv('SMTP_PASS'),
|
||||
'email_from' => 'orders@merchbay.com',
|
||||
'email_from_name' => 'Merchbay Daily Order Report',
|
||||
'email_to' => 'graphics@crewsportswear.com',
|
||||
'email_bcc' => ['webmaster@crewsportswear.com', 'production@crewsportswear.com'],
|
||||
'report_url' => 'https://www.crewsportswear.com/email_reports_merchbay/daily_reports_with_image.php',
|
||||
'admin_url' => 'https://merchbay.app',
|
||||
'csv_dir' => 'daily_order_reports_merchbay',
|
||||
'log_prefix' => '[MERCHBAY]'
|
||||
]
|
||||
];
|
||||
|
||||
$cfg = $config[$brand];
|
||||
|
||||
// Connect to brand-specific database
|
||||
try {
|
||||
$conn = new PDO(
|
||||
"mysql:host={$cfg['db_host']};dbname={$cfg['db_name']}",
|
||||
$cfg['db_user'],
|
||||
$cfg['db_pass']
|
||||
);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch(PDOException $pe) {
|
||||
$msg = $dateTimeToday . "\t\t\t{$cfg['log_prefix']} Database error: " . $pe->getMessage() . "\n";
|
||||
file_put_contents('email.log', print_r($msg, true), FILE_APPEND);
|
||||
die('Connection error, because: ' .$pe->getMessage());
|
||||
}
|
||||
|
||||
// Query orders
|
||||
$q = $conn->prepare("SELECT t.StoreName, o.CartKey, o.ProductName, o.NAME, o.NUMBER, o.Size, o.JerseySize, o.ShortsSize, o.Price, o.Quantity, (o.Price * o.Quantity) AS TotalPrice, ((o.Price * o.Quantity) * 0.10) AS Tax,
|
||||
o.DateCreated, pd.InvoiceNumber, pd.Payer_Email FROM orders AS o
|
||||
INNER JOIN payment_details AS pd ON pd.CartKey = o.CartKey
|
||||
INNER JOIN teamstores AS t ON t.Id = o.StoreId
|
||||
WHERE o.DateCreated BETWEEN '$dateToday 00:00:00' AND '$dateToday 23:59:00'
|
||||
ORDER BY o.DateCreated");
|
||||
$q->execute();
|
||||
$result = $q->rowCount();
|
||||
|
||||
if($result > 0){
|
||||
$invoice_array = array();
|
||||
|
||||
// Create CSV directory if it doesn't exist
|
||||
if (!file_exists($cfg['csv_dir'])) {
|
||||
mkdir($cfg['csv_dir'], 0755, true);
|
||||
}
|
||||
|
||||
$filename = $cfg['csv_dir'] . '/daily_order_report_'.$dateToday.'.csv';
|
||||
$headers = array('StoreName', 'ProductName', 'NAME', 'NUMBER', 'Size', 'JerseySize', 'ShortsSize', 'Price', 'Quantity', 'TotalPrice', 'Tax', 'DateCreated', 'InvoiceNumber', 'Payer_Email');
|
||||
$fp = fopen($filename, 'w');
|
||||
fputcsv($fp, $headers);
|
||||
|
||||
while ($row = $q->fetch()) {
|
||||
$value = $row['CartKey'] . "|" . $row['InvoiceNumber'];
|
||||
if(!in_array($value, $invoice_array, true)){
|
||||
array_push($invoice_array, $value);
|
||||
}
|
||||
|
||||
$lineData = array($row['StoreName'], $row['ProductName'], $row['NAME'], $row['NUMBER'], $row['Size'], $row['JerseySize'], $row['ShortsSize'], $row['Price'], $row['Quantity'], $row['TotalPrice'], $row['Tax'], $row['DateCreated'], $row['InvoiceNumber'], $row['Payer_Email']);
|
||||
fputcsv($fp, $lineData);
|
||||
}
|
||||
fclose($fp);
|
||||
|
||||
// Build email body
|
||||
$report_link = $cfg['report_url'] . "?d=" . $dateToday;
|
||||
$body = "<html>";
|
||||
$body .= "<body>";
|
||||
$body .= "<p>Please download the attached file for today's order report.</p>";
|
||||
$body .= "<br><br>";
|
||||
$body .= "<p>For the order report with image please <a href='".$report_link."'>click here</a>.</p>";
|
||||
$body .= "<p>####################################</p>";
|
||||
$body .= "<p>Order Details per Invoice</p>";
|
||||
|
||||
foreach($invoice_array as $invoice){
|
||||
$v = explode("|", $invoice);
|
||||
$body .= "<div>Invoice Number: <a href='" . $cfg['admin_url'] . "/admin/orders/view/" . $v['0'] . "/print'>" . $v['1'] . "</a></div>";
|
||||
}
|
||||
|
||||
$body .= "</body>";
|
||||
$body .= "</html>";
|
||||
|
||||
// Send email
|
||||
$mail = new PHPMailer();
|
||||
$mail->IsSMTP();
|
||||
$mail->SMTPAuth = true;
|
||||
// $mail->SMTPDebug = 3; // uncomment for debug mode.
|
||||
|
||||
$mail->SMTPSecure = 'tls';
|
||||
$mail->Host = getenv('SMTP_HOST') ?: 'smtp.gmail.com';
|
||||
$mail->Port = getenv('SMTP_PORT') ?: '587';
|
||||
|
||||
$mail->Username = $cfg['smtp_user'];
|
||||
$mail->Password = $cfg['smtp_pass'];
|
||||
|
||||
$mail->SetFrom($cfg['email_from'], $cfg['email_from_name']);
|
||||
$mail->addAddress($cfg['email_to']);
|
||||
|
||||
foreach ($cfg['email_bcc'] as $bcc) {
|
||||
$mail->addBCC($bcc);
|
||||
}
|
||||
|
||||
$mail->addAttachment($filename);
|
||||
|
||||
$mail->Subject = "{$cfg['email_from_name']} - $dateToday";
|
||||
$mail->Body = $body;
|
||||
$mail->ErrorInfo;
|
||||
$mail->IsHTML(true);
|
||||
|
||||
if(!$mail->Send()){
|
||||
$msg = $dateTimeToday . "\t\t\t{$cfg['log_prefix']} Mailer Error: " . $mail->ErrorInfo . "\n";
|
||||
file_put_contents('email.log', print_r($msg, true), FILE_APPEND);
|
||||
} else {
|
||||
$msg = $dateTimeToday . "\t\t\t{$cfg['log_prefix']} successfully sent ({$result} orders)\n";
|
||||
file_put_contents('email.log', print_r($msg, true), FILE_APPEND);
|
||||
}
|
||||
|
||||
} else {
|
||||
$msg = $dateTimeToday . "\t\t\t{$cfg['log_prefix']} No order for today\n";
|
||||
file_put_contents('email.log', print_r($msg, true), FILE_APPEND);
|
||||
}
|
||||
356
styles.css
Normal file
356
styles.css
Normal file
@@ -0,0 +1,356 @@
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 880px;
|
||||
padding: 10px;
|
||||
width: 880px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 880px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn > tbody > tr > td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class="body"] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class="body"] p,
|
||||
table[class="body"] ul,
|
||||
table[class="body"] ol,
|
||||
table[class="body"] td,
|
||||
table[class="body"] span,
|
||||
table[class="body"] a {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
table[class="body"] .wrapper,
|
||||
table[class="body"] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class="body"] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class="body"] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class="body"] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class="body"] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class="body"] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class="body"] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
.table-bordered td,
|
||||
th {
|
||||
border: 1px solid black;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.previewImage {
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.items tr {
|
||||
page-break-inside: avoid;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@page {
|
||||
size: 18cm 26.7cm;
|
||||
margin: 1cm;
|
||||
}
|
||||
|
||||
#tbl_subitem tr td {
|
||||
text-align: center;
|
||||
}
|
||||
Reference in New Issue
Block a user