Compare commits

..

46 Commits
main ... jadi

Author SHA1 Message Date
Baghaztra
28a8c1e63d [Update] Fix date picker component 2025-10-24 05:33:44 +07:00
Baghaztra
9ccc26455e Penyesuaian tabel dan SEO 2025-10-24 05:16:15 +07:00
adityaalfarison
8ad64a986d update 23 oktober 2025-10-23 17:11:25 +07:00
Baghaztra
3895c43a68 [Update] Pindahkan item dari nampan ke brankas 2025-10-23 13:02:28 +07:00
Baghaztra
63c85ce2c7 [Update] Footer 2025-10-23 09:57:40 +07:00
Baghaztra
3b618c6747 Update .gitignore 2025-10-22 18:03:40 +07:00
Baghaztra
5580b53bfd Merge branch 'development' into jadi 2025-10-22 17:55:29 +07:00
ee12b9c8d8 update gitignore 2025-10-22 11:32:27 +07:00
6c1f5b4edc update config 2025-10-22 11:29:38 +07:00
dhilanradya
0bbbba4a11 Merge branch 'development' of https://git.abbauf.com/Magang-2025/Kasir into development 2025-10-22 11:07:24 +07:00
dhilanradya
d55399ed55 [fix] perbaikan input select + font qr code 2025-10-22 11:04:53 +07:00
Baghaztra
a37102c3ae [Update] docker container version 2025-10-22 08:57:12 +07:00
Baghaztra
9e7ef1b825 Update docker-compose.yml 2025-10-21 17:54:09 +07:00
Baghaztra
3809b295a5 Create Searchbar.vue 2025-10-21 17:41:04 +07:00
Baghaztra
48ffd8ac57 Delete searchbar.vue 2025-10-21 17:40:47 +07:00
Baghaztra
178c09c3c4 ... 2025-10-21 17:38:56 +07:00
timotiabbauftech
b186e7ca23 Merge branch 'development' of https://git.abbauf.com/Magang-2025/Kasir into development 2025-10-21 10:07:27 +07:00
timotiabbauftech
f957cf4e5a fix layout 2025-10-21 10:06:30 +07:00
dhilanradya
98a8096725 Merge branch 'development' of https://git.abbauf.com/Magang-2025/Kasir into development 2025-10-21 10:02:45 +07:00
dhilanradya
65e1bdd116 [fix] perbaikan transaksi code dan seeder sales 2025-10-21 10:02:02 +07:00
Baghaztra
f2b1ba34a7 Update README.md 2025-10-21 09:49:49 +07:00
Baghaztra
e82c4c6d91 [Update] Print label layout 2025-10-21 09:43:23 +07:00
dhilanradya
7cd3e54402 fixing error 2025-10-20 17:28:57 +07:00
dhilanradya
d48ec1a1fd [Update] Fix kode item error 2025-10-20 16:50:26 +07:00
dhilanradya
4d1bebc6a6 [update] rapihin dikit 2025-10-20 11:29:52 +07:00
dhilanradya
945cea3b4c [update] 1 lembar 2025-10-20 10:21:45 +07:00
Baghaztra
b44eb2cdc3 Penyesuaian 2025-10-17 16:25:38 +07:00
Baghaztra
42de65fc6b [Update] Docker 2025-10-17 16:24:26 +07:00
Baghaztra
0dc0cb365f [Refactor] rename import 2025-10-17 15:28:42 +07:00
Baghaztra
06ec582ffb Merge branch 'development' into production 2025-10-17 11:16:29 +07:00
dhilanradya
d442e2e691 [update] dari struoverlay langsung ke strukVIew 2025-10-17 10:45:53 +07:00
adityaalfarison
878e652630 Merge branch 'development' of https://git.abbauf.com/Magang-2025/Kasir into development 2025-10-16 17:06:29 +07:00
adityaalfarison
bab572e2ca update gambar struk 2025-10-16 17:06:18 +07:00
Baghaztra
6a42f15822 Merge branch 'development' into production 2025-10-16 14:38:55 +07:00
Baghaztra
508d636675 Update DatabaseSeeder.php 2025-10-16 14:38:33 +07:00
Baghaztra
a0100af842 Merge branch 'development' into production 2025-10-16 14:32:39 +07:00
Baghaztra
271a1e3660 [Refactor] Fix edit produk 2025-10-16 14:30:39 +07:00
timotiabbauftech
065c21e07c Update EditProduk.vue 2025-10-16 14:13:32 +07:00
adityaalfarison
ecc9385c28 [Update] Print barcode 2025-10-16 11:12:27 +07:00
Baghaztra
e1639109c8 [Update] Cetak barcode 2025-10-15 20:47:15 +07:00
Baghaztra
e54a021b98 Update README.md 2025-10-15 14:48:28 +07:00
Baghaztra
8665584567 [Update] print label
Library niimblue tidak digunakan, namun potongan kode tetap disimpan
2025-10-15 14:33:47 +07:00
Baghaztra
c8559d63df Merge branch 'development' of https://git.abbauf.com/Magang-2025/Kasir into development 2025-10-15 10:02:11 +07:00
Baghaztra
e226faf08a [Update] Docker 2025-10-15 10:02:06 +07:00
dhilanradya
43e058fe6f Merge branch 'development' of https://git.abbauf.com/Magang-2025/Kasir into development 2025-10-15 09:15:26 +07:00
dhilanradya
899a81c709 rapihin 2025-10-15 09:14:30 +07:00
382 changed files with 6563 additions and 1427 deletions

View File

@ -1,74 +1,32 @@
# ========================================
# Docker Ignore File
# ========================================
# Dependencies
node_modules/
vendor/
# Git
.git
.gitignore
.gitattributes
# Build artifacts
/public/build
/public/hot
/public/storage
/storage/*.key
# Documentation
README.md
CHANGELOG.md
Documentation/
*.md
# Environment files
.env
.env.backup
.env.production
# Tests
tests/
phpunit.xml
.phpunit.cache
.phpunit.result.cache
# IDE & Editors
# IDE files
.vscode/
.idea/
.fleet/
.nova/
.zed/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
.phpactor.json
# Docker files (tidak perlu di-copy ke image)
Dockerfile
docker-compose*.yml
.dockerignore
# Environment files (akan di-set via environment variables)
.env
.env.*
!.env.example
# Dependencies (akan di-install via composer & npm)
/vendor/
/node_modules/
# Build artifacts (akan di-build di stage terpisah)
/public/hot
/public/build
# Storage & Cache (akan di-mount sebagai volume)
/storage/*.key
/storage/logs/*
/storage/framework/cache/*
/storage/framework/sessions/*
/storage/framework/views/*
/storage/pail
# Generated files
/public/storage
/bootstrap/cache/*
# Local development files
Homestead.json
Homestead.yaml
auth.json
.vagrant
.phpstorm.meta.php
_ide_helper.php
# Logs
*.log
@ -76,7 +34,86 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS Files
.DS_Store
Thumbs.db
desktop.ini
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Laravel specific
/bootstrap/cache/*.php
/storage/app/*
!/storage/app/.gitignore
/storage/framework/cache/*
!/storage/framework/cache/.gitignore
/storage/framework/sessions/*
!/storage/framework/sessions/.gitignore
/storage/framework/testing/*
!/storage/framework/testing/.gitignore
/storage/framework/views/*
!/storage/framework/views/.gitignore
/storage/logs/*
!/storage/logs/.gitignore
# Testing
/coverage
/.phpunit.result.cache
/phpunit.xml
# Backup files
*.bak
*.backup
*.sql
# Documentation
README.md
Documentation/

View File

@ -1,86 +1,65 @@
# ========================================
# Abbauf Kasir - Docker Environment Configuration
# ========================================
# Copy file ini ke .env sebelum deployment
# Ganti semua placeholder dengan nilai yang sesuai
# Application
APP_NAME=Abbauf-Kasir
APP_ENV=production
APP_KEY= # Generate dengan: php artisan key:generate
APP_DEBUG=false
APP_URL=http://localhost # Ganti dengan domain production
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=id
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=id_ID
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
# Logging
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=error
LOG_LEVEL=debug
# Database Configuration
DB_CONNECTION=mysql
DB_HOST=mysql # Nama service di docker-compose
DB_PORT=3306
DB_DATABASE=kasir_db # Nama database
DB_USERNAME=kasir_user # Username database
DB_PASSWORD=strong_password_here # GANTI dengan password kuat!
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
# MySQL Root Password (untuk docker-compose)
MYSQL_ROOT_PASSWORD=root_strong_password # GANTI dengan password root yang kuat!
# Cache & Session
CACHE_STORE=redis
SESSION_DRIVER=redis
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
# Redis Configuration
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Queue
QUEUE_CONNECTION=redis
# Broadcasting
BROADCAST_CONNECTION=log
# Filesystem
FILESYSTEM_DISK=local
# Mail Configuration (opsional)
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="noreply@abbauf-kasir.local"
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
# AWS (jika menggunakan S3 untuk storage)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
# Ports (untuk docker-compose)
APP_PORT=80 # Port untuk akses aplikasi
# DB_PORT=3306 # Uncomment jika ingin expose DB port
# REDIS_PORT=6379 # Uncomment jika ingin expose Redis port
# Vite
VITE_APP_NAME="${APP_NAME}"
VITE_APP_NAME="${APP_NAME}"

View File

@ -1,7 +1,7 @@
APP_NAME=Abbauf-Kasir
APP_ENV=production
APP_ENV=local
APP_KEY=
APP_DEBUG=false
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en

5
.gitignore vendored
View File

@ -24,6 +24,5 @@ Homestead.yaml
Thumbs.db
# Docker - Database Backups (exclude backups from git)
/docker/mysql/backups/*.sql
/docker/mysql/backups/*.sql.gz
!/docker/mysql/backups/README.md
/docker
deploy-kasir.sh

View File

@ -1,91 +1,57 @@
# ========================================
# Stage 1: Build Frontend Assets (Vue.js)
# ========================================
FROM node:20-alpine as node_builder
WORKDIR /app
# Copy package files untuk caching layer
COPY package*.json ./
# Install dependencies
RUN npm ci --legacy-peer-deps
# Copy seluruh source code
COPY . .
# Build production assets
RUN npm run build
# ========================================
# Stage 2: Laravel Application
# ========================================
FROM php:8.3-fpm-alpine
# Install system dependencies dan PHP extensions
RUN apk update && apk add --no-cache \
git \
unzip \
libzip-dev \
libpng-dev \
oniguruma-dev \
libxml2-dev \
curl \
mysql-client \
autoconf \
g++ \
make \
&& docker-php-ext-install \
pdo_mysql \
zip \
gd \
mbstring \
exif \
pcntl \
bcmath \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& apk del autoconf g++ make \
&& rm -rf /var/cache/apk/* /tmp/*
# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Use PHP 8.2 with Apache
FROM php:8.2-apache
# Set working directory
WORKDIR /var/www/html
# Copy composer files untuk caching layer
COPY composer.json composer.lock ./
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
libzip-dev \
zip \
unzip \
nodejs \
npm \
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
# Install Redis extension
RUN pecl install redis && docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Enable Apache mod_rewrite
RUN a2enmod rewrite
# Copy Apache configuration
COPY docker/apache/000-default.conf /etc/apache2/sites-available/000-default.conf
# Copy application files
COPY . /var/www/html
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html/storage \
&& chmod -R 755 /var/www/html/bootstrap/cache
# Install PHP dependencies
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
RUN composer install --no-dev --optimize-autoloader
# Copy application source code
COPY . .
# Install Node.js dependencies and build assets
RUN npm install && npm run build
# Copy hasil build Vue dari stage 1
COPY --from=node_builder /app/public/build /var/www/html/public/build
# Create .env file if it doesn't exist
RUN if [ ! -f .env ]; then cp .env.example .env; fi
# Generate autoload files dengan optimasi
RUN composer dump-autoload --optimize --classmap-authoritative
# Generate application key
RUN php artisan key:generate
# Create required directories dan set permissions
RUN mkdir -p \
storage/framework/cache \
storage/framework/sessions \
storage/framework/views \
storage/logs \
bootstrap/cache \
&& chown -R www-data:www-data \
/var/www/html/storage \
/var/www/html/bootstrap/cache \
&& chmod -R 775 \
/var/www/html/storage \
/var/www/html/bootstrap/cache
# Expose port 80
EXPOSE 80
# Switch ke user non-root untuk keamanan
USER www-data
EXPOSE 9000
CMD ["php-fpm"]
# Start Apache
CMD ["apache2-foreground"]

View File

@ -0,0 +1,717 @@
# 📊 Diagram Alur Kerja Printer Niimbot
## 🔄 Alur Lengkap: Dari UI ke Printer
```mermaid
graph TD
A[User buka halaman Brankas] --> B{Printer sudah<br/>terhubung?}
B -->|Tidak| C[Klik 'Hubungkan Printer']
B -->|Ya| D[Status: Terhubung]
C --> E[Modal NiimbotConnector muncul]
E --> F{Pilih metode koneksi}
F -->|Bluetooth| G[Klik 'Hubungkan Printer']
F -->|USB/Serial| G
G --> H[Browser: Dialog pairing muncul]
H --> I[User pilih Niimbot device]
I --> J[Library: initClient & connect]
J --> K{Koneksi<br/>berhasil?}
K -->|Tidak| L[Tampilkan error]
K -->|Ya| M[Fetch printer info]
M --> N[Status: Terhubung ✓]
N --> D
D --> O[User klik item di Brankas]
O --> P[Modal item muncul]
P --> Q[Generate QR Code URL]
Q --> R[Tampilkan preview QR]
R --> S[User klik 'Cetak ke Niimbot']
S --> T{Printer<br/>terhubung?}
T -->|Tidak| U[Alert: Hubungkan printer]
U --> C
T -->|Ya| V[createQRLabelCanvas]
V --> W[Load QR image]
W --> X[Draw ke canvas:<br/>QR + Kode + Nama + Berat]
X --> Y[Convert canvas to DataURL]
Y --> Z[printQRCode composable]
Z --> AA[Stop heartbeat]
AA --> AB[Create PrintTask]
AB --> AC[ImageEncoder.encodeCanvas]
AC --> AD[printTask.printInit]
AD --> AE[printTask.printPage]
AE --> AF{Print<br/>sukses?}
AF -->|Tidak| AG[Tampilkan error]
AF -->|Ya| AH[printTask.printEnd]
AH --> AI[Start heartbeat]
AI --> AJ[Alert: Berhasil dicetak!]
AJ --> AK[Printer cetak label]
```
---
## 🏗️ Arsitektur Komponen
```
┌─────────────────────────────────────────────────────────────┐
│ BrankasList.vue │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ UI Layer │ │
│ │ - Tombol "Hubungkan Printer" │ │
│ │ - Tombol "Cetak ke Niimbot" │ │
│ │ - Modal item dengan QR Code │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Logic Layer │ │
│ │ - printQR() → Trigger print │ │
│ │ - createQRLabelCanvas() → Generate canvas dengan QR │ │
│ │ - handlePrinterConnected() → Event handler │ │
│ └────────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ useNiimbotPrinter.js (Composable) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ State Management │ │
│ │ - printerClient (reactive) │ │
│ │ - connectionState │ │
│ │ - isPrinting, printProgress │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Methods │ │
│ │ - initClient() → Buat NiimbotClient │ │
│ │ - connect() → Hubungkan ke printer │ │
│ │ - disconnect() → Putuskan koneksi │ │
│ │ - printQRCode() → Print image ke printer │ │
│ └────────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
@mmote/niimbluelib (Library) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ NiimbotBluetoothClient / NiimbotSerialClient │ │
│ │ - connect() → Web Bluetooth/Serial API │ │
│ │ - fetchPrinterInfo() → Get printer metadata │ │
│ │ - startHeartbeat() → Maintain connection │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ PrintTask │ │
│ │ - newPrintTask() → Buat task print │ │
│ │ - printInit() → Inisialisasi print │ │
│ │ - printPage() → Kirim data gambar │ │
│ │ - waitForFinished() → Tunggu selesai │ │
│ │ - printEnd() → Akhiri print │ │
│ └────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ ImageEncoder │ │
│ │ - encodeCanvas() → Encode canvas ke binary │ │
│ └────────────────────────────────────────────────────────┘ │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Browser Web APIs │
│ - Web Bluetooth API (navigator.bluetooth) │
│ - Web Serial API (navigator.serial) │
└───────────────────────┬─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Printer Niimbot (Hardware) │
│ - Terima perintah via BLE/USB │
│ - Decode binary image data │
│ - Print ke label thermal │
└─────────────────────────────────────────────────────────────┘
```
---
## 📡 Sequence Diagram: Proses Print
```
User BrankasList useNiimbot niimbluelib Browser API Printer
│ │ │ │ │ │
│ Klik item │ │ │ │ │
├───────────────>│ │ │ │ │
│ │ │ │ │ │
│ Modal muncul │ │ │ │ │
<───────────────┤ │ │ │ │
│ │ │ │ │ │
│ Klik 'Cetak' │ │ │ │ │
├───────────────>│ │ │ │ │
│ │ │ │ │ │
│ │ printQRCode()│ │ │ │
│ ├─────────────>│ │ │ │
│ │ │ │ │ │
│ │ │ stopHeartbeat() │ │
│ │ ├─────────────>│ │ │
│ │ │ │ │ │
│ │ │ newPrintTask() │ │
│ │ ├─────────────>│ │ │
│ │ │ │ │ │
│ │ │ encodeCanvas() │ │
│ │ ├─────────────>│ │ │
│ │ │ │ │ │
│ │ │ │ BLE/Serial │ │
│ │ │ │ Write Data │ │
│ │ │ ├─────────────>│ │
│ │ │ │ │ │
│ │ │ │ │ Send Data │
│ │ │ │ ├───────────>│
│ │ │ │ │ │
│ │ │ │ │ Printing │
│ │ │ │ │ ........ │
│ │ │ │ │ │
│ │ │ │ Status ACK │ │
│ │ │ │<─────────────┤ │
│ │ │ │ │ │
│ │ │ printEnd() │ │ │
│ │ ├─────────────>│ │ │
│ │ │ │ │ │
│ │ │ startHeartbeat() │ │
│ │ ├─────────────>│ │ │
│ │ │ │ │ │
│ │ Alert sukses │ │ │ │
│ │<─────────────┤ │ │ │
│ │ │ │ │ │
│ Label tercetak│ │ │ │ │
<───────────────┴──────────────┴──────────────┴──────────────┴────────────┤
│ │
```
---
## 🔌 Connection Flow Detail
### Bluetooth Connection
```
1. User Action
└─> Klik "Hubungkan Printer"
2. initClient('bluetooth')
└─> new NiimbotBluetoothClient()
3. connect()
└─> navigator.bluetooth.requestDevice({
filters: [{ namePrefix: 'Niimbot' }],
optionalServices: [SERVICE_UUID]
})
4. Browser Dialog
└─> User pilih device "Niimbot-XXXX"
5. device.gatt.connect()
└─> Establish BLE connection
6. getPrimaryService(SERVICE_UUID)
└─> getCharacteristic(TX_CHAR, RX_CHAR)
7. Event: 'connect' triggered
└─> connectionState = 'connected'
8. fetchPrinterInfo()
└─> Send command: GET_INFO
└─> Receive: model, serial, firmware version
9. startHeartbeat()
└─> Kirim ping setiap 1 detik
└─> Cek printer masih hidup
```
### USB/Serial Connection
```
1. User Action
└─> Klik "Hubungkan Printer"
2. initClient('serial')
└─> new NiimbotSerialClient()
3. connect()
└─> navigator.serial.requestPort({
filters: [{ usbVendorId: 0xXXXX }]
})
4. Browser Dialog
└─> User pilih USB device
5. port.open({ baudRate: 9600 })
└─> Establish serial connection
6. Setup reader/writer streams
└─> readable.getReader()
└─> writable.getWriter()
7. Event: 'connect' triggered
└─> connectionState = 'connected'
8. fetchPrinterInfo() & startHeartbeat()
└─> (sama dengan Bluetooth)
```
---
## 🖼️ Image Processing Pipeline
```
QR Code URL (dari API)
new Image()
img.onload
Create Canvas (384x384)
├─> Fill white background
├─> drawImage(qr, x, y, size, size)
├─> fillText(kode_item)
├─> fillText(nama_produk)
├─> fillText(berat)
canvas.toDataURL('image/png')
loadImageToCanvas(dataUrl)
[Optional] applyThreshold(canvas, 140)
├─> getImageData()
├─> for each pixel:
│ avg = (r+g+b)/3
│ if avg < 140: black
│ else: white
├─> putImageData()
ImageEncoder.encodeCanvas(canvas, 'top')
├─> Convert to 1-bit bitmap
├─> Rotate if needed (printDirection)
├─> Pack bits: 8 pixels = 1 byte
Binary Data (EncodedImage)
printTask.printPage(encoded, quantity)
Send to Printer via BLE/Serial
```
---
## 📊 State Machine Diagram
```
┌─────────────────┐
│ DISCONNECTED │◄────────┐
└────────┬────────┘ │
│ │
│ connect() │ disconnect()
│ │
▼ │
┌─────────────────┐ │
│ CONNECTING │ │
└────────┬────────┘ │
│ │
│ success │ error
│ │
▼ │
┌─────────────────┐ │
│ CONNECTED │─────────┤
└────────┬────────┘ │
│ │
│ printQRCode() │
│ │
▼ │
┌─────────────────┐ │
│ PRINTING │ │
│ [progress: X%] │ │
└────────┬────────┘ │
│ │
│ finished │
│ │
▼ │
┌─────────────────┐ │
│ CONNECTED │─────────┘
│ (heartbeat) │
└─────────────────┘
```
---
## 🎯 Data Flow: QR Code → Printed Label
```
┌────────────────────────────────────────────────────────────┐
│ 1. DATA SOURCE │
│ ─────────────────────────────────────────────────────────│
│ selectedItem = { │
│ kode_item: "BRN-001", │
│ produk: { │
│ nama: "Cincin Emas 24K", │
│ berat: 5.2 │
│ } │
│ } │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 2. QR CODE GENERATION (External API) │
│ ─────────────────────────────────────────────────────────│
│ URL: https://api.qrserver.com/v1/create-qr-code/ │
│ ?size=150x150&data=BRN-001 │
│ │
│ Returns: image/png (base64 or URL) │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 3. CANVAS CREATION │
│ ─────────────────────────────────────────────────────────│
│ createQRLabelCanvas(qrUrl, item) │
│ │
│ Canvas 384x384px: │
│ ┌──────────────────────┐ │
│ │ │ │
│ │ ┌──────────┐ │ ← QR Code (200x200) │
│ │ │ ▓▓ ▓▓▓▓│ │ │
│ │ │▓ ▓▓ ▓ │ │ │
│ │ │ ▓▓▓ ▓▓▓│ │ │
│ │ └──────────┘ │ │
│ │ │ │
│ │ BRN-001 │ ← Kode Item (bold 18px) │
│ │ Cincin Emas 24K │ ← Nama Produk (14px) │
│ │ 5.2g │ ← Berat (12px) │
│ │ │ │
│ └──────────────────────┘ │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 4. IMAGE ENCODING │
│ ─────────────────────────────────────────────────────────│
│ ImageEncoder.encodeCanvas(canvas, 'top') │
│ │
│ Process: │
│ - Convert RGB to grayscale │
│ - Apply threshold (< 140 = black, >= 140 = white) │
│ - Pack 8 pixels into 1 byte (1-bit bitmap) │
│ - Width: 384px = 48 bytes per row │
│ - Height: 384 rows │
│ - Total: 48 × 384 = 18,432 bytes │
│ │
│ Output: Uint8Array(18432) │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 5. PRINT PROTOCOL │
│ ─────────────────────────────────────────────────────────│
│ printTask.printInit() │
│ └─> Send: [CMD_INIT, density, labelType, ...] │
│ │
│ printTask.printPage(encoded, quantity) │
│ └─> Send in chunks: │
│ [CMD_IMAGE_HEADER, width, height] │
│ [CMD_IMAGE_DATA, chunk1...] │
│ [CMD_IMAGE_DATA, chunk2...] │
│ ... │
│ [CMD_IMAGE_END] │
│ │
│ printTask.waitForFinished() │
│ └─> Poll status every 100ms │
│ until status = FINISHED │
│ │
│ printTask.printEnd() │
│ └─> Send: [CMD_PRINT_END] │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ 6. PHYSICAL OUTPUT │
│ ─────────────────────────────────────────────────────────│
│ Printer Niimbot: │
│ - Receives binary data via BLE/USB │
│ - Thermal head heats selected pixels │
│ - Paper feeds forward │
│ - Label printed with QR + text │
│ - Cut (automatic or manual) │
│ │
│ Result: Physical label 40x40mm dengan QR code │
└────────────────────────────────────────────────────────────┘
```
---
## 🧩 Component Interaction Map
```
┌──────────────────────┐
│ App.vue / Router │
└──────────┬───────────┘
┌──────────────────────┐
│ BrankasList.vue │
│ │
│ ┌────────────────┐ │
│ │ Header │ │
│ │ - Printer Btn │──┼──┐
│ └────────────────┘ │ │
│ │ │
│ ┌────────────────┐ │ │
│ │ Item List │ │ │
│ │ - Click item │──┼──┼──┐
│ └────────────────┘ │ │ │
│ │ │ │
│ ┌────────────────┐ │ │ │
│ │ Item Modal │ │ │ │
│ │ - QR Preview │ │ │ │
│ │ - Print Btn │──┼──┼──┼──┐
│ └────────────────┘ │ │ │ │
└──────────────────────┘ │ │ │
│ │ │
┌─────────────────────────────────────┘ │ │
│ │ │
▼ │ │
┌──────────────────┐ │ │
│ NiimbotConnector │ │ │
│ .vue │ │ │
│ │ │ │
│ - Connect UI │◄───────────────────────────┘ │
│ - Status display │ │
│ - Printer info │ │
└────────┬─────────┘ │
│ │
│ uses │
│ │
▼ │
┌──────────────────────────────────────────────────┼───┐
│ useNiimbotPrinter.js (Composable) │ │
│ │ │
│ - State: printerClient, connectionState, etc │◄──┘
│ - Methods: connect(), disconnect(), printQR() │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Event Listeners Setup │ │
│ │ - on('connect') │ │
│ │ - on('disconnect') │ │
│ │ - on('printprogress') │ │
│ │ - on('heartbeat') │ │
│ └─────────────────────────────────────────────┘ │
└──────────────────┬───────────────────────────────┘
│ imports
┌────────────────────────────────────────────────────┐
@mmote/niimbluelib
│ │
│ - NiimbotBluetoothClient │
│ - NiimbotSerialClient │
│ - ImageEncoder │
│ - PrintTask abstraction │
│ - Utils │
└──────────────────┬─────────────────────────────────┘
│ uses
┌────────────────────────────────────────────────────┐
│ Browser APIs │
│ - navigator.bluetooth (Web Bluetooth API) │
│ - navigator.serial (Web Serial API) │
└────────────────────────────────────────────────────┘
```
---
## ⚡ Performance Timeline
```
Event Timeline (typical print job):
0ms │ User clicks "Cetak ke Niimbot"
10ms │ Check printer connection
│ └─> Already connected ✓
20ms │ createQRLabelCanvas() start
│ └─> Load QR image
150ms │ Image loaded, draw to canvas
│ └─> Draw QR, text, etc
170ms │ canvas.toDataURL()
200ms │ printQRCode() called
│ └─> stopHeartbeat()
220ms │ newPrintTask()
240ms │ ImageEncoder.encodeCanvas()
│ └─> RGB → grayscale
│ └─> Threshold
│ └─> Pack to 1-bit
450ms │ Encoding complete (18KB data)
470ms │ printTask.printInit()
│ └─> Send init command
490ms │ ACK received
500ms │ printTask.printPage()
│ └─> Send image header
520ms │ Send data chunk 1/10
540ms │ Send data chunk 2/10
... │ ...
740ms │ Send data chunk 10/10
750ms │ All data sent
780ms │ waitForFinished() polling start
800ms │ Status: PRINTING (0%)
900ms │ Status: PRINTING (25%)
1000ms │ Status: PRINTING (50%)
1100ms │ Status: PRINTING (75%)
1200ms │ Status: PRINTING (100%)
1220ms │ Status: FINISHED
1230ms │ printTask.printEnd()
1250ms │ startHeartbeat()
1260ms │ Alert: "QR Code berhasil dicetak!"
Total: ~1.3 seconds dari klik hingga selesai
```
---
## 🔐 Security & Privacy
```
Data Flow Security:
┌──────────────────┐
│ User's Browser │
└────────┬─────────┘
│ HTTPS (encrypted)
┌──────────────────┐
│ QR API Server │ ← External: api.qrserver.com
└────────┬─────────┘ (Kirim kode_item only)
│ Returns image URL
┌──────────────────┐
│ User's Browser │
│ - Generate │
│ canvas │
│ - Encode image │
└────────┬─────────┘
│ BLE/USB (direct, tidak via internet)
│ Encrypted jika BLE
┌──────────────────┐
│ Niimbot Printer │ ← Local device, tidak terkoneksi internet
└──────────────────┘
Privacy Notes:
- Data tidak lewat server backend aplikasi
- QR Code dibuat real-time di browser
- Gambar langsung dikirim ke printer lokal
- Tidak ada logging data item ke cloud
```
---
## 📈 Error Handling Flow
```
┌──────────────────┐
│ User Action │
└────────┬─────────┘
┌──────────────────┐
│ Try: connect() │
└────────┬─────────┘
┌────────────┴────────────┐
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Success │ │ Error │
└──────┬──────┘ └──────┬──────┘
│ │
│ ▼
│ ┌────────────────┐
│ │ Catch error │
│ │ - Log to │
│ │ console │
│ │ - Set error │
│ │ message │
│ │ - Alert user │
│ └────────┬───────┘
│ │
│ ▼
│ ┌────────────────┐
│ │ connectionState│
│ │ = 'disconnected│
│ └────────────────┘
┌────────────────┐
│ fetchPrinterInfo│
└────────┬────────┘
┌──────┴──────┐
│ │
┌─────▼─────┐ ┌────▼────┐
│ Success │ │ Error │
└─────┬─────┘ └────┬────┘
│ │
│ └──> Log & continue (non-critical)
┌─────────────────┐
│ startHeartbeat()│
└─────────┬───────┘
└──> Connected & Ready
```
---
Semua diagram ini membantu memahami bagaimana sistem bekerja end-to-end! 🎉

View File

@ -0,0 +1,373 @@
# 🖨️ Dokumentasi Integrasi Printer Niimbot
## 📋 Ringkasan
Aplikasi kasir sekarang mendukung pencetakan QR Code langsung ke printer Niimbot menggunakan library `@mmote/niimbluelib` yang diadaptasi dari proyek niimblue.
---
## 🔧 Teknologi & Library
### Library Utama
- **@mmote/niimbluelib** - Library JavaScript untuk komunikasi dengan printer Niimbot
- Mendukung Web Bluetooth API
- Mendukung Web Serial API (USB)
- Encoding gambar ke format printer
### Browser Support
| Browser | Bluetooth | USB (Serial) |
|---------|-----------|--------------|
| Chrome/Edge (Desktop) | ✅ | ✅ |
| Chrome (Android) | ✅ | ❌ |
| Firefox | ❌ | ❌ |
| Safari | ❌ | ❌ |
**Rekomendasi:** Gunakan Chrome atau Edge versi terbaru
---
## 📁 Struktur File
```
Kasir/resources/js/
├── composables/
│ └── useNiimbotPrinter.js # Composable untuk koneksi & print
├── components/
│ ├── NiimbotConnector.vue # Modal untuk koneksi printer
│ └── BrankasList.vue # Modifikasi: tambah fitur print Niimbot
```
---
## 🚀 Cara Kerja
### 1⃣ Alur Koneksi Printer
```
User klik "Hubungkan Printer"
→ Modal NiimbotConnector muncul
→ Pilih metode: Bluetooth / USB
→ Browser munculkan dialog pairing
→ User pilih printer Niimbot
→ Koneksi terjalin
→ Fetch info printer
→ Status: "Terhubung"
```
### 2⃣ Alur Print QR Code
```
User klik item di Brankas
→ Modal popup dengan QR Code
→ User klik "Cetak ke Niimbot"
→ Cek koneksi printer:
- Jika belum terhubung → tampilkan modal koneksi
- Jika sudah terhubung → lanjut
→ Generate canvas dengan QR + info item
→ Encode canvas ke format printer
→ Kirim ke printer via library
→ Status: "Mencetak... X%"
→ Selesai
```
### 3⃣ Komponen Kunci
#### `useNiimbotPrinter.js` (Composable)
```javascript
// State management
- printerClient // Instance NiimbotClient
- connectionState // 'disconnected' | 'connecting' | 'connected'
- connectedPrinterName // Nama printer yang terhubung
- printerInfo // Info printer (model, serial, dll)
- isPrinting // Status sedang print
- printProgress // Progress print (0-100)
// Methods
- initClient(type) // Init client (bluetooth/serial)
- connect() // Hubungkan ke printer
- disconnect() // Putuskan koneksi
- printQRCode(dataUrl, options) // Print QR code image
```
#### `NiimbotConnector.vue` (Component)
- Modal untuk koneksi/disconnect printer
- Pilih metode: Bluetooth atau USB
- Tampilkan status koneksi
- Tampilkan info printer detail
#### `BrankasList.vue` (Modified)
- Tombol "Hubungkan Printer" di header
- Tombol "Cetak ke Niimbot" di modal item
- Tombol "Browser" sebagai fallback
- Alert sukses/error
---
## 🛠️ Instalasi
### 1. Install Library
```bash
cd "c:\Data\Magang\Toko perhiasan\Kasir"
npm install @mmote/niimbluelib
```
### 2. Files Sudah Dibuat
- ✅ `resources/js/composables/useNiimbotPrinter.js`
- ✅ `resources/js/components/NiimbotConnector.vue`
- ✅ `resources/js/components/BrankasList.vue` (modified)
### 3. Build Assets
```bash
npm run build
# atau untuk development
npm run dev
```
---
## 📖 Cara Penggunaan
### A. Menghubungkan Printer
1. **Persiapan Printer:**
- Nyalakan printer Niimbot
- Pastikan kertas label sudah terpasang
- Untuk Bluetooth: Aktifkan mode pairing (biasanya tahan tombol power)
2. **Di Aplikasi:**
- Buka halaman Brankas
- Klik tombol "Hubungkan Printer" (di kanan atas)
- Pilih metode koneksi (Bluetooth/USB)
- Klik "Hubungkan Printer"
- Browser akan menampilkan dialog - pilih printer Niimbot Anda
- Tunggu hingga status berubah "Terhubung"
### B. Mencetak QR Code
1. **Pilih Item:**
- Klik item yang ingin dicetak QR-nya
- Modal akan muncul dengan QR Code
2. **Cetak:**
- Klik tombol "Cetak ke Niimbot"
- Jika printer belum terhubung, akan muncul peringatan
- Progress print akan ditampilkan (Mencetak... X%)
- Tunggu hingga selesai
3. **Alternatif:**
- Klik tombol "Browser" untuk print menggunakan printer biasa
---
## ⚙️ Konfigurasi Print
Di file `BrankasList.vue`, fungsi `printQR()`:
```javascript
await printQRCode(imageDataUrl, {
density: 3, // Kepadatan tinta (1-5, default: 3)
quantity: 1, // Jumlah cetakan (default: 1)
labelType: 1, // Tipe label:
// 1 = WithGaps (label dengan jarak)
// 2 = Continuous (tanpa jarak)
// 3 = WithHoles (dengan lubang)
printTaskName: 'D110' // Model printer (D110, B21, B1, dll)
// Auto-detect jika tidak cocok
});
```
### Ukuran Label
Di fungsi `createQRLabelCanvas()`:
```javascript
const labelWidth = 384; // pixel (40mm @ 240dpi)
const labelHeight = 384; // pixel
```
**Sesuaikan dengan ukuran label Anda:**
- 30mm x 30mm ≈ 288 x 288 px
- 40mm x 40mm ≈ 384 x 384 px
- 50mm x 30mm ≈ 480 x 288 px
Rumus: `mm × (dpi / 25.4)`
- DPI printer Niimbot umumnya: 203 atau 240
---
## 🐛 Troubleshooting
### 1. "Browser tidak mendukung koneksi printer"
**Solusi:**
- Gunakan Chrome atau Edge versi terbaru
- Update browser ke versi terkini
- Pastikan menggunakan HTTPS (Web Bluetooth hanya jalan di HTTPS)
### 2. "Gagal terhubung ke printer"
**Solusi:**
- Pastikan printer sudah dinyalakan
- Pastikan Bluetooth/USB aktif
- Coba matikan dan nyalakan ulang printer
- Untuk Bluetooth: pastikan tidak paired ke device lain
- Coba refresh halaman dan ulangi koneksi
### 3. "Printer terhubung tapi tidak print"
**Solusi:**
- Periksa kertas label sudah terpasang dengan benar
- Coba disconnect dan connect ulang
- Periksa battery printer (untuk model portable)
- Cek ukuran canvas sesuai dengan ukuran label
### 4. "Print hasil blur/tidak jelas"
**Solusi:**
- Tingkatkan `density` (misal dari 3 ke 4)
- Pastikan threshold image sudah optimal
- Ukuran QR Code jangan terlalu kecil (min 150x150px)
### 5. "Error: printTaskName not compatible"
**Solusi:**
- Ganti `printTaskName` sesuai model printer:
- D110: `'D110'`
- B21: `'B21'`
- B1: `'B1'`
- Atau hapus parameter, biarkan auto-detect
---
## 🔍 Debugging
### Enable Console Logs
Library sudah dilengkapi logging:
```javascript
// Di browser console akan muncul:
>> Packet sent: [hex bytes]
<< Packet received: [hex bytes]
Printer connected: { deviceName: "Niimbot-XXXX" }
Printer info fetched: { model: "D110", ... }
```
### Test Print Canvas
Tambahkan debug preview sebelum print:
```javascript
// Di createQRLabelCanvas, sebelum resolve(canvas):
document.body.appendChild(canvas); // Tampilkan canvas di halaman
canvas.style.border = '1px solid red';
```
---
## 📚 Referensi API
### useNiimbotPrinter Composable
#### State (Reactive)
```javascript
const {
printerClient, // NiimbotClient instance
connectionState, // 'disconnected' | 'connecting' | 'connected'
connectedPrinterName, // string
printerInfo, // object { model, serial, ... }
printerMeta, // object { densityMin, densityMax, ... }
heartbeatData, // object { powerLevel, ... }
isPrinting, // boolean
printProgress, // number (0-100)
// Computed
isConnected, // boolean
isDisconnected, // boolean
featureSupport, // { webBluetooth, webSerial, ... }
} = useNiimbotPrinter();
```
#### Methods
```javascript
// Init client
initClient(type: 'bluetooth' | 'serial'): void
// Connect/Disconnect
connect(): Promise<void>
disconnect(): void
// Print
printQRCode(
imageDataUrl: string,
options?: {
density?: number, // 1-5
quantity?: number, // jumlah print
labelType?: number, // 1=WithGaps, 2=Continuous, 3=WithHoles
printTaskName?: string // 'D110', 'B21', dll
}
): Promise<{ success: boolean, message: string }>
// Utils
loadImageToCanvas(dataUrl: string): Promise<HTMLCanvasElement>
applyThreshold(canvas: HTMLCanvasElement, threshold?: number): HTMLCanvasElement
```
---
## 🎨 Customization
### Ubah Desain Label
Edit fungsi `createQRLabelCanvas()` di `BrankasList.vue`:
```javascript
// Contoh: Tambah logo toko
const logo = new Image();
logo.src = '/path/to/logo.png';
logo.onload = () => {
ctx.drawImage(logo, 10, 10, 50, 50);
};
// Contoh: Tambah border
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.strokeRect(5, 5, labelWidth - 10, labelHeight - 10);
// Contoh: Font custom
ctx.font = 'bold 20px "Courier New"';
```
### Tambah Barcode
Install library barcode:
```bash
npm install jsbarcode
```
Implementasi:
```javascript
import JsBarcode from 'jsbarcode';
const barcodeCanvas = document.createElement('canvas');
JsBarcode(barcodeCanvas, item.kode_item, {
format: 'CODE128',
width: 2,
height: 40,
});
ctx.drawImage(barcodeCanvas, x, y);
```
---
## 📝 Catatan Penting
1. **HTTPS Required**: Web Bluetooth & Web Serial hanya bekerja di HTTPS (kecuali localhost)
2. **User Gesture**: Koneksi harus dipicu oleh user action (klik button), tidak bisa otomatis on load
3. **One Printer**: Satu browser session hanya bisa terhubung ke 1 printer
4. **Battery**: Printer portable akan disconnect otomatis jika battery rendah
5. **Label Size**: Sesuaikan ukuran canvas dengan ukuran label fisik untuk hasil optimal
---
## 🆘 Support
Jika ada masalah:
1. Periksa console browser (F12 → Console)
2. Periksa kompatibilitas browser
3. Periksa dokumentasi printer Niimbot Anda
4. Issue library: https://github.com/MultiMote/niimbluelib
---
## 📄 License
Menggunakan library `@mmote/niimbluelib` yang bersifat open-source.
Pastikan mematuhi lisensi library saat deploy production.

192
README.md
View File

@ -66,50 +66,7 @@ Semuah sistem Point of Sale (POS) yang dirancang khusus untuk kebutuhan toko per
---
## <20> Docker Installation (Recommended)
### Quick Start dengan Docker
```bash
# 1. Clone repository
git clone https://git.abbauf.com/Magang-2025/Kasir.git
cd Kasir
# 2. Setup environment
copy .env.docker .env # Windows
# atau
cp .env.docker .env # Linux/Mac
# 3. Edit .env (PENTING: ganti password!)
notepad .env # Windows
nano .env # Linux/Mac
# 4. Deploy dengan satu command
docker-deploy.bat # Windows
# atau
./docker-deploy.sh # Linux/Mac
# 5. Akses aplikasi
# http://localhost
```
**Dokumentasi Docker Lengkap:**
- 📖 Quick Start: [README-DOCKER.md](README-DOCKER.md)
- 📚 Full Guide: [DOCKER-DEPLOYMENT.md](DOCKER-DEPLOYMENT.md)
- 📋 Checklist: [DEPLOYMENT-CHECKLIST.md](DEPLOYMENT-CHECKLIST.md)
- 🎯 Quick Reference: [QUICK-REFERENCE.txt](QUICK-REFERENCE.txt)
**Management Commands:**
```bash
docker-helper.bat status # Cek status containers
docker-helper.bat logs # Lihat logs
docker-helper.bat backup # Backup database
docker-helper.bat restart # Restart containers
```
---
## <20>🔧 Manual Installation
## Installation
### 1. Clone Repository
@ -151,8 +108,6 @@ DB_USERNAME=root
DB_PASSWORD=
```
---
### 5. Setup Database
```bash
@ -177,6 +132,44 @@ php artisan storage:link
npm run dev
```
#### Production Mode
```bash
# Pastikan env production sudah tersedia
docker compose --env-file .env.production up -d --build
# Siapkan database
docker exec -it abbauf_kasir_app php artisan migrate --seed
# Atau import database secara manual
docker exec -i abbauf_kasir_db mysql -u kasir_user -pkasir_password kasir_db < ./storage/toko_emas.sql
# Periksa database (opsional)
docker exec -it abbauf_kasir_db bash
mysql -u kasir_user -pkasir_password kasir_db
```
### 8. Print Label
- Install driver, ada pada folder `./driver/NiimbotPrinterDriverInstall_3.0.0.5.exe` untuk windows 64bit.
- Pilih `NIIMBOT B3S_P` pada saat install driver.
- Sambungkan printer ke komputer via USB.
- Nyalakan printer.
- Temukan kode qr yang akan diprint (misalnya pada halaman brankas)
- Klik tombol print pada halaman tersebut
- Pilih printer `NIIMBOT B3S_P` dan atur kertas ke ukuran kertas `40mm x 30mm`, margin `Default`, scale `Default`
- Klik print
### 9. Print Nota
- Pastikan printer terhubung dengan komputer via USB.
- Nyalakan printer.
- Install driver, ada pada folder `./driver/L120_x64_213UsHomeExportAsiaML.exe`.
- Lakukan transaksi penjualan pada aplikasi, atau pilih nota yang akan diprint di `Laporan > Riwayat transaksi`.
- Klik tombol print pada halaman tersebut
- Pilih ukuran kertas A4, margin `Minimum`, scale `95`
- Klik print
---
## 🌐 Akses Aplikasi
@ -184,19 +177,18 @@ npm run dev
Setelah instalasi berhasil, akses aplikasi melalui:
- **URL**: http://localhost:8000
- **Admin Panel**: http://localhost:8000/admin (jika tersedia)
### 👤 Default Login
**Owner Account:**
- Email: `owner@tokoperhiasan.com`
- Password: `password123`
- Username: `admin`
- Password: `123123`
**Kasir Account:**
- Email: `kasir@tokoperhiasan.com`
- Password: `password123`
- Username: `kasir`
- Password: `123123`
---
@ -229,10 +221,6 @@ kasir-toko-perhiasan/
---
Oke, jadi penjelasan “📊 Fitur Database” yang kamu tulis nggak sepenuhnya sesuai sama struktur tabel yang udah kamu definisikan di awal. Ada tabel yang salah nama, ada juga relasi yang kebalik. Gue rapihin biar konsisten dengan skema yang udah kamu kasih:
---
## 📊 Fitur Database (Revisi)
### Tabel Utama
@ -275,100 +263,6 @@ ItemTransaksi -> belongsTo -> Item
---
## 🛠️ Development
### Code Quality
```bash
npm run dev
```
### Database Management
```bash
# Reset database dan re-seed
php artisan migrate:fresh --seed
# Backup database
php artisan backup:run
# Generate model dengan migration
php artisan make:model ProductCategory -m
```
### Production Deployment
**Dengan Docker (Recommended):**
```bash
# 1. Setup .env untuk production
copy .env.docker .env
# Edit: APP_ENV=production, APP_DEBUG=false, set password kuat
# 2. Deploy
docker-deploy.bat
# 3. Monitor
docker-helper.bat status
docker-helper.bat logs
```
**Manual Deployment:**
```bash
# Build assets
npm run build
# Optimize Laravel
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Set permissions
chmod -R 775 storage bootstrap/cache
```
**Dokumentasi lengkap:** [DOCKER-DEPLOYMENT.md](DOCKER-DEPLOYMENT.md)
---
## 🔄 Update Aplikasi
**Dengan Docker:**
```bash
docker-helper.bat update # Otomatis: pull, rebuild, migrate, cache
```
**Manual:**
```bash
git pull origin main
composer install --no-dev
npm install && npm run build
php artisan migrate --force
php artisan config:cache
```
---
## 💾 Backup & Restore
**Backup Database (Docker):**
```bash
docker-helper.bat backup
# File tersimpan di: docker/mysql/backups/
```
**Restore Database (Docker):**
```bash
docker-helper.bat restore
# Pilih file backup yang tersedia
```
**Manual Backup:**
```bash
mysqldump -u root -p toko_emas > backup.sql
```
---
## 📄 License
Lisensi dan kepemilikan atascource code adalah milik PT Teknologi Mulia Sejahtera Cemerlang.

View File

@ -18,7 +18,7 @@ class AuthController extends Controller
]);
// cari user berdasarkan nama
$user = User::where('nama', $request->nama)->first();
$user = User::whereRaw('BINARY nama = ?', [$request->nama])->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json([

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Item;
use App\Models\Nampan;
use Illuminate\Http\Request;
class ItemController extends Controller
@ -66,13 +67,20 @@ class ItemController extends Controller
{
$validated = $request->validate([
'id_produk' => 'required|exists:produks,id',
'id_nampan' => 'nullable|exists:nampans,id'
'id_nampan' => 'required',
],[
'id_produk' => 'Id produk tidak valid.',
'id_nampan' => 'Id nampan tidak valid'
]);
$item = Item::findOrFail($id)->update($validated);
if ($validated['id_nampan'] == 0) {
$item = Item::findOrFail($id)->update([
'id_produk' => $validated['id_produk'],
'id_nampan' => null
]);
} else {
$item = Item::findOrFail($id)->update($validated);
}
return response()->json([
'message' => 'Item berhasil diubah',

View File

@ -75,6 +75,7 @@ class TransaksiController extends Controller
'kasir',
'sales',
'itemTransaksi.produk',
'itemTransaksi.produk.foto',
'itemTransaksi' => function ($query) {
$query->orderBy('created_at', 'asc');
}
@ -103,7 +104,7 @@ class TransaksiController extends Controller
'ongkos_bikin' => 'nullable|numeric|min:0',
'total_harga' => 'required|numeric',
'items' => 'required|array',
'items.*.kode_item' => 'required|exists:items,id|numeric',
'items.*.kode_item' => 'required',
'items.*.harga_deal' => 'required|numeric',
]);

View File

@ -23,24 +23,15 @@ class Item extends Model
{
parent::boot();
static::creating(function ($item) {
$prefix = 'TMJC';
$date = now()->format('Ymd');
static::created(function ($item) {
if (!$item->kode_item || $item->kode_item === 'belum pak') {
$prefix = "TMJC";
$date = $item->created_at->format('Ymd');
$number = str_pad($item->id, 4, '0', STR_PAD_LEFT);
// Cari item terakhir yg dibuat hari ini
$lastItem = self::whereDate('created_at', now()->toDateString())
->orderBy('id', 'desc')
->first();
$number = 1;
if ($lastItem && $lastItem->kode_item) {
// Ambil 4 digit terakhir dari kode_item
$lastNumber = intval(substr($lastItem->kode_item, -4));
$number = $lastNumber + 1;
$item->kode_item = $prefix . $date . $number;
$item->save();
}
// Format: ITM202509090001
$item->kode_item = $prefix . $date . str_pad($number, 4, '0', STR_PAD_LEFT);
});
}

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

View File

@ -12,10 +12,10 @@ return new class extends Migration
public function up()
{
Schema::table('items', function (Blueprint $table) {
$table->string('kode_item')->unique()->after('id');
$table->string('kode_item')->unique()->default('belum pak')->after('id');
});
}
public function down()
{
Schema::table('items', function (Blueprint $table) {

View File

@ -5,7 +5,6 @@ namespace Database\Seeders;
use App\Models\Kategori;
use App\Models\Nampan;
use App\Models\Produk;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DataSeeder extends Seeder
@ -16,47 +15,60 @@ class DataSeeder extends Seeder
public function run(): void
{
// Nampan
for ($i=0; $i < 30; $i++) {
for ($i = 0; $i < 30; $i++) {
if ($i != 12) {
Nampan::factory()->create([
'nama' => 'A' . ($i + 1)
Nampan::create([
'nama' => 'A' . ($i + 1),
'created_at' => now(),
'updated_at' => now(),
]);
}
}
// Kategori
$kategoriList = ['Cincin', 'Gelang Rantai', 'Gelang Bulat', 'Kalung', 'Liontin', 'Anting', 'Giwang'];
foreach ($kategoriList as $kategori) {
Kategori::factory()->create([
'nama' => $kategori
foreach ($kategoriList as $index => $kategori) {
Kategori::create([
'nama' => $kategori,
'created_at' => now(),
'updated_at' => now(),
]);
}
// Produk
$produk1 = Produk::factory()->create([
'nama'=>'Gelang serut daun shimmer mp (mas putih)',
'id_kategori'=>Kategori::find(2),
'berat'=>1.4,
'kadar'=>8,
'harga_per_gram'=>900000,
'harga_jual'=>1260000,
// Produk 1
$produk1 = Produk::create([
'nama' => 'Gelang serut daun shimmer mp (mas putih)',
'id_kategori' => Kategori::where('nama', 'Gelang Rantai')->first()->id,
'berat' => 1.4,
'kadar' => 8,
'harga_per_gram' => 900000,
'harga_jual' => 1260000,
'created_at' => now(),
'updated_at' => now(),
]);
$produk1->foto()->create([
'id_produk'=>$produk1->id,
'url'=>'https://i.imgur.com/eGYHzvw.jpeg'
'id_produk' => $produk1->id,
'url' => 'https://i.imgur.com/eGYHzvw.jpeg',
'created_at' => now(),
'updated_at' => now(),
]);
$produk2 = Produk::factory()->create([
'nama'=>'Gelang rantai 5 buah clover merah',
'id_kategori'=>Kategori::find(2),
'berat'=>3.6,
'kadar'=>8,
'harga_per_gram'=>850000,
'harga_jual'=>3060000,
// Produk 2
$produk2 = Produk::create([
'nama' => 'Gelang rantai 5 buah clover merah',
'id_kategori' => Kategori::where('nama', 'Gelang Rantai')->first()->id,
'berat' => 3.6,
'kadar' => 8,
'harga_per_gram' => 850000,
'harga_jual' => 3060000,
'created_at' => now(),
'updated_at' => now(),
]);
$produk2->foto()->create([
'id_produk'=>$produk2->id,
'url'=>'https://i.imgur.com/UjQzYoE.jpeg'
'id_produk' => $produk2->id,
'url' => 'https://i.imgur.com/UjQzYoE.jpeg',
'created_at' => now(),
'updated_at' => now(),
]);
}
}

View File

@ -9,8 +9,8 @@ use App\Models\Produk;
use App\Models\Sales;
use App\Models\Transaksi;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class DatabaseSeeder extends Seeder
{
@ -19,24 +19,27 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
User::factory()->create([
// Create admin user
User::create([
'nama' => 'admin',
'role' => 'owner',
'password' => bcrypt('123123'),
'password' => Hash::make('123123'),
'created_at' => now(),
'updated_at' => now(),
]);
User::factory()->create([
// Create kasir user
User::create([
'nama' => 'kasir',
'role' => 'kasir',
'password' => bcrypt('123123'),
]);
Sales::factory()->create([
'nama' => 'Umum',
'no_hp' => '-',
'alamat' => '-',
'password' => Hash::make('123123'),
'created_at' => now(),
'updated_at' => now(),
]);
// Call other seeders
$this->call(DataSeeder::class);
// $this->call(DummySeeder::class);
$this->call(DummySeeder::class);
}
}

View File

@ -1,176 +1,79 @@
version: '3.8'
services:
# ========================================
# Laravel PHP-FPM Application
# ========================================
laravel:
app:
build:
context: .
dockerfile: Dockerfile
container_name: abbauf_kasir_app
container_name: kasir-app
restart: unless-stopped
working_dir: /var/www/html
volumes:
# Mount storage untuk uploads dan logs (persistent)
- ./storage:/var/www/html/storage
# Mount public build assets (read-only)
- ./public/build:/var/www/html/public/build:ro
environment:
# Application
APP_NAME: ${APP_NAME:-Abbauf-Kasir}
APP_ENV: ${APP_ENV:-production}
APP_KEY: ${APP_KEY}
APP_DEBUG: ${APP_DEBUG:-false}
APP_URL: ${APP_URL:-http://localhost}
# Database
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE:-kasir_db}
DB_USERNAME: ${DB_USERNAME:-kasir_user}
DB_PASSWORD: ${DB_PASSWORD}
# Cache & Session
CACHE_STORE: redis
SESSION_DRIVER: redis
REDIS_HOST: redis
REDIS_PORT: 6379
# Queue
QUEUE_CONNECTION: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- kasir_network
healthcheck:
test: ["CMD", "php-fpm", "-t"]
interval: 30s
timeout: 10s
retries: 3
# ========================================
# Nginx Web Server
# ========================================
nginx:
image: nginx:alpine
container_name: abbauf_kasir_nginx
restart: unless-stopped
- ./:/var/www/html
- ./docker/mysql/backups:/var/www/html/docker/mysql/backups
ports:
- "${APP_PORT:-80}:80"
volumes:
# Nginx configuration
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
# Laravel public directory (untuk static assets)
- ./public:/var/www/html/public:ro
# Storage symlink untuk file uploads
- ./storage/app/public:/var/www/html/public/storage:ro
- "8000:80"
environment:
- APP_ENV=local
- APP_DEBUG=true
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=kasir_db
- DB_USERNAME=kasir_user
- DB_PASSWORD=kasir_password
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- laravel
networks:
- kasir_network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
- mysql
- redis
# ========================================
# MySQL Database
# ========================================
mysql:
image: mysql:8.4
container_name: abbauf_kasir_db
image: mysql:5.7
container_name: kasir-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root_secret_password}
MYSQL_DATABASE: ${DB_DATABASE:-kasir_db}
MYSQL_USER: ${DB_USERNAME:-kasir_user}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
ports:
- "${DB_PORT:-3306}:3306"
MYSQL_DATABASE: kasir_db
MYSQL_ROOT_PASSWORD: kasir_password
MYSQL_USER: kasir_user
MYSQL_PASSWORD: kasir_password
volumes:
- mysql_data:/var/lib/mysql
# Optional: backup folder
- ./docker/mysql/mysql_data:/var/lib/mysql
- ./docker/mysql/backups:/backups
networks:
- kasir_network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-root_secret_password}"]
interval: 10s
timeout: 5s
retries: 5
command: --default-authentication-plugin=mysql_native_password
ports:
- "3308:3306"
# ========================================
# Redis Cache & Session Store
# ========================================
redis:
image: redis:7-alpine
container_name: abbauf_kasir_redis
container_name: kasir-redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-6379}:6379"
- "6380:6379"
volumes:
- redis_data:/data
networks:
- kasir_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
command: redis-server --appendonly yes
- ./docker/redis/redis_data:/data
# ========================================
# Queue Worker (Optional - untuk background jobs)
# ========================================
queue:
build:
context: .
dockerfile: Dockerfile
container_name: abbauf_kasir_queue
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./storage:/var/www/html/storage
environment:
APP_ENV: ${APP_ENV:-production}
APP_KEY: ${APP_KEY}
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE:-kasir_db}
DB_USERNAME: ${DB_USERNAME:-kasir_user}
DB_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis
QUEUE_CONNECTION: redis
depends_on:
- laravel
- redis
- mysql
networks:
- kasir_network
command: php artisan queue:work --tries=3 --timeout=90
# phpmyadmin:
# image: phpmyadmin/phpmyadmin
# container_name: kasir-phpmyadmin
# restart: unless-stopped
# environment:
# PMA_HOST: mysql
# PMA_PORT: 3306
# PMA_USER: ${DB_USERNAME}
# PMA_PASSWORD: ${DB_PASSWORD}
# ports:
# - "8080:80"
# depends_on:
# - mysql
# networks:
# - kasir-network
# volumes:
# mysql_data:
# driver: local
# redis_data:
# driver: local
# ========================================
# Networks
# ========================================
networks:
kasir_network:
driver: bridge
# ========================================
# Persistent Volumes
# ========================================
volumes:
mysql_data:
driver: local
redis_data:
driver: local
default:
external: true
name: kasir_kasir_network

View File

@ -0,0 +1,12 @@
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/public
<Directory /var/www/html/public>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

View File

@ -0,0 +1,2 @@
[auto]
server-uuid=79710a58-aeaa-11f0-a626-0242ac150002

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,135 @@
10,69
10,29
10,93
10,2
10,3
11,13
11,4
11,8
11,17
11,15
11,16
11,7
11,2
11,3
9,5
9,2
9,3
8,7
8,5
8,2
8,3
0,457
12,2
12,1
12,3
20,2
20,1
20,3
19,2
19,1
19,3
18,1
18,3
14,1
11,28
11,27
11,26
14,5
11,25
11,24
14,3
13,1
11,23
11,22
13,3
11,21
11,20
11,1
11,19
10,303
10,302
10,301
10,300
10,299
10,298
10,297
10,296
2,2
10,295
2,1
10,294
10,293
10,292
2,3
10,291
10,290
10,289
10,288
10,287
10,286
10,285
10,284
10,283
10,282
10,281
10,280
10,279
10,278
10,277
10,276
10,275
10,274
10,273
10,272
10,271
10,270
10,269
10,268
10,267
10,266
10,265
10,264
10,263
10,262
10,261
10,260
10,259
10,258
10,257
10,256
10,255
10,254
10,253
10,252
10,251
10,250
10,249
10,248
10,247
10,246
10,245
10,244
10,243
10,242
10,241
10,240
10,239
10,238
10,237
10,236
10,235
10,234
10,233
10,232
10,231
10,230
10,229
10,228
10,227
10,226
10,225
10,224
10,223
10,222
10,221

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
default-character-set=latin1
default-collation=latin1_swedish_ci

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
/var/run/mysqld/mysqld.sock

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
ÿlocalhost performance_schema mysql.session ÿlocalhost sys mysql.sys ÿ% kasir\_db kasir_user 

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
default-character-set=latin1
default-collation=latin1_swedish_ci

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More