Synaplan
기업을 위한 오픈소스 AI 플랫폼
Welcome to the OpenMes repository 👋 This project was shaped through consultations with several manufacturing companies across different industries - if you spot a bug, please let me know. Live demo: https://demo.getopenmes.com/register
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Powerful, flexible, and tablet-ready MES for small manufacturers
🚀 Try the live demo → demo-2.getopenmes.com Free demo account — active for 30 days, no credit card required
OpenMES is a self-hosted, open-source MES (Manufacturing Execution System) built for small and mid-sized manufacturers — woodworking, metal fabrication, plastics and assembly shops — who need real production tracking without enterprise complexity or licensing costs.
Drag-and-drop production scheduling with Gantt-style views across multiple production lines.
OpenMES is built to be extended! Use our comprehensive hook system to add custom functionality without modifying core code.
A module drops into backend/modules/, is toggled in Admin → Modules, and
plugs into three kinds of extension point — with zero runtime cost when
disabled:
WorkOrderCreated, WorkOrderUpdated, WorkOrderCompletedBatchCreated, StepStarted, StepCompletedWorkstationStateChanged, MachineMessageReceivedUserAssignedToLineResourceChanged — a generic CRUD hook for any curated resource (work orders, customers, materials, lines, …) on create/update/deleteWorkOrderScheduled — a work order is placed/moved on the plannerMenuRegistry) — add links and dropdowns to the sidebarWidgetRegistry) — add cards to the admin dashboard// In your module's ServiceProvider::boot()
use App\Events\WorkOrder\WorkOrderCompleted;
use App\Services\MenuRegistry;
use Illuminate\Support\Facades\Event;
// React to an order completing (observe — never mutate core state here)
Event::listen(WorkOrderCompleted::class, function ($event) {
ExternalErp::notifyCompletion($event->workOrder);
});
// Add a link to the sidebar
app(MenuRegistry::class)->addItem('production', 'My Page', url('/modules/mine'));
🛠️ Build a module (step-by-step tutorial): backend/modules/README.md — from an empty folder to a running module, plus a code map of how it all works
📚 Hook reference: HOOKS.md — every event, menu and widget hook, with payloads and examples
📁 Reference Modules: backend/modules/ExampleShowcase (all hooks) · backend/modules/ExampleHooks (minimal)
OpenMES ships with optional modules that can be enabled from Admin → Modules.
Dedicated station for scanning finished products with a barcode reader (EAN/QR) before shipping or warehouse handoff.
How it works:
/operator/packaging on a dedicated workstation or tablet (supervisors and admins reach the same screen at /packaging/station)packed_qty counterFeatures:
Admin → Packaging → EAN Codes)packed_qty resets each shift; shift boundaries are configurablephp artisan packaging:reset-shift resets all counters immediatelyRoutes:
| URL | Access | Description |
|---|---|---|
/operator/packaging | Operator | Scanning station (operator shell) |
/packaging/station | Supervisor, Admin | Scanning station (admin shell; operators are redirected to /operator/packaging) |
/packaging/ | Supervisor, Admin | Admin overview |
/packaging/eans | Supervisor, Admin | EAN code management |
Required migrations (run automatically on first deploy):
create_work_order_eans_table
create_packaging_scan_logs_table
add_packed_qty_to_work_orders_table
OpenMES uses a dead-simple Laravel monolith architecture:
┌─────────────────┐
│ Laravel App │ :80 (serves everything)
│ (Blade + API) │
└────────┬────────┘
│
┌────▼─────┐
│ PostgreSQL│
└──────────┘
Stack:
OpenMES architecture maps onto the ISA-95 / IEC 62264 standard for Manufacturing Operations Management at Level 3. See docs/isa95.md for the full coverage matrix across Production, Maintenance, Quality, and Inventory operations.
One command — clone and run the installer:
git clone https://github.com/Mes-Open/OpenMes.git
cd OpenMes
./install.sh
install.sh generates secure credentials into .env, auto-selects a free host port (80 if it's available, otherwise the next free one — e.g. 8080), builds the app from the cloned source, and starts it in production. When it finishes it prints your URL and admin login. Use ./install.sh --yes to accept all defaults non-interactively.
Windows? In PowerShell (Docker Desktop) run the equivalent installer — same behaviour as install.sh:
.\install.ps1
(Under WSL2 just use ./install.sh.)
Prefer plain Compose? docker compose up -d --build also works — it builds from source and serves on port 80 (override with HTTP_PORT/HTTPS_PORT in .env if 80 is taken).
With Docker, the database is migrated and the admin account is created
automatically on first boot (from the credentials in .env) — no manual wizard
step is needed. Open the URL the installer printed and log in.
Running OpenMES outside Docker (bare PHP)? A web-based setup wizard guides you through database and admin configuration the first time you open the site.
Containers not starting?
# Check container logs
docker-compose logs backend
docker-compose logs postgres
# Restart containers
docker-compose restart
# Rebuild containers (if needed)
docker-compose down
docker-compose build --no-cache
docker-compose up -d
Database connection errors?
# Make sure postgres is healthy
docker-compose ps
# Check database credentials
grep DB_PASSWORD .env backend/.env
# Restart backend
docker-compose restart backend
Application not loading?
# Check if services are running
docker-compose ps
# View backend logs
docker-compose logs -f backend
# Rebuild backend (includes asset build)
docker-compose build --no-cache backend
docker-compose up -d
Port 80 already in use?
# Check what's using port 80
sudo lsof -i :80
# Edit docker-compose.yml to use different port:
# Change: - "80:8000" to "8080:8000"
# Then access at: http://localhost:8080
Benefits:
OpenMES reports on itself once a day — versions, which features are switched on, rough size bands, and where errors occur (class, file and line). It never sends anything you entered into OpenMES: no material or product codes, no lot numbers, no order data, no recipes, no personal data, and no error message text. That boundary is enforced by tests, not by good intentions.
It is on by default. Switch it off in Settings → System → Usage reporting,
or set OPENMES_TELEMETRY=false before first start. An installation with no
route to the internet behaves exactly like a connected one — nothing breaks,
nothing slows down, and the log does not fill up.
See what would be sent, at any time, with php artisan telemetry:send --dry-run.
Full details in docs/telemetry.md.
We welcome contributions! Whether it's bug reports, feature requests, documentation, or code - we'd love your help.
See CONTRIBUTING.md for details.
OpenMES is incrementally adopting React via Inertia.js alongside the existing Blade + Livewire UI. Both render trees coexist — new pages can opt into React without touching anything else.
The point of this setup: pull the repo, drop it on a server (FTP/SSH/whatever), edit .jsx files in place, refresh the browser. No npm install on your laptop, no build step in your hands. A container handles it.
Start the stack with the dev overlay:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
That overlay (docker-compose.dev.yml):
frontend container running vite build --watch. It watches backend/resources/ and rebuilds backend/public/build/ in ~100ms whenever you save a file.app/, routes/, resources/, public/, …) into the backend container so PHP/Blade edits are live too.Workflow:
backend/resources/js/Pages/Foo.jsx (or any source file).docker compose logs -f frontend if you want to see it.Create the page component at backend/resources/js/Pages/Foo.jsx:
import { Head } from '@inertiajs/react';
export default function Foo({ greeting }) {
return (
<>
<Head title="Foo" />
<h1>{greeting}</h1>
</>
);
}
Add a route in backend/routes/web.php:
Route::get('/foo', fn () => Inertia::render('Foo', [
'greeting' => 'Hello from Laravel',
]));
Visit /foo. Props from the controller arrive as React props.
Production deployments do not use docker-compose.dev.yml. The image's Dockerfile already runs npm ci && npm run build at image build time, so the production container ships with pre-built assets — no Node process at runtime.
OpenMES live-syncs read data from Laravel to React/mobile clients over a single WebSocket. Clients subscribe to a named collection (a server-defined query) rather than querying tables directly: they load one snapshot over HTTP, then receive deltas as they happen. Writes still go through Laravel controllers as before.
1. snapshot: client ─► GET /api/collections/{name} (CollectionController, authed + tenant-scoped)
◄─ { rows: [...], at: <unix ts> }
2. deltas: Laravel ─► CollectionChanged ─► Reverb ─► private channel col.{tenant}.{collection}
◄─ insert / update / delete, applied by the client's collection adapter
writes: client ─► Laravel controllers (validation, auth, Eloquent events) ─► Postgres
app/Sync/ShapeRegistry.php +
app/Sync/Shapes/): each entry pins a table, a column allowlist and an
optional server-built where. Clients request collections by name, never by
table. CollectionController serves the snapshot; App\Events\CollectionChanged
broadcasts each row change.col.{tenantKey}.{collection}), authorized
in routes/channels.php via the session cookie at /broadcasting/auth./app/* to Reverb. One multiplexed WebSocket carries every
collection channel, so the browser's ~6-connection HTTP/1.1 cap doesn't apply
and no PHP worker is ever held open.lib/echo.js (one app-wide laravel-echo connection) and
lib/realtimeCollection.js, which feeds a TanStack DB collection.Two files:
Define the shape. backend/app/Sync/Shapes/MyShape.php:
class MyShape extends Shape {
public function table(): string { return 'my_table'; }
public function columns(): array { return ['id', 'name', 'status']; }
public function where(User $user): ?string {
return "tenant_id = {$user->tenant_id}";
}
}
Simple lookup tables can skip the class and use an inline
['table', 'columns', 'where'?] config instead.
Register it. Add to the $shapes map in backend/app/Sync/ShapeRegistry.php:
'my_collection_name' => MyShape::class,
Then subscribe from React — the hooks in lib/useSyncedShape.js handle the
snapshot and the channel for you:
const { data, isLoading } = useSyncedShape('my_collection_name');
useLiveShape is the same function under a different name (call-site intent
only). Hot app-wide collections are shared via LiveShapesProvider — don't
subscribe to them again per page.
columns() method
is the whitelist. Sensitive columns (password hashes, tokens, PII) simply
aren't listed."g" collections).octane:start is the Dockerfile CMD) — a concurrent, in-memory runtime. The
old php artisan serve was single-threaded and serialized every request; it
remains only as a documented dev fallback. Octane keeps the framework booted
between requests, so watch for state that assumes a fresh boot per request
(singletons, static props) — validate the full app under Octane before a
production rollout.CollectionChanged implements
ShouldBroadcastNow because QUEUE_CONNECTION=sync; a write's broadcast
happens inline with the request. Moving to a real queue driver would make
delivery async — check the latency assumptions in the UI before doing so.WidgetRegistry
extension API where modules registered Blade views into named zones
(admin_dashboard.kpi, admin_dashboard.main, admin_dashboard.sidebar). The
React dashboard does not render these. No bundled module currently uses the
registry, so nothing actively breaks — but a future React-based widget extension
API needs to be designed before third-party modules can extend the new dashboard.OpenMES uses a layered / dual-licensing model:
modules/) — Academic Free License 3.0 (AFL-3.0) (permissive; modules may be closed/proprietary) — see LICENSE-AFL-3.0.txt.Under AGPL-3.0 you can use, modify, distribute and use OpenMES privately, provided you disclose source for network/distributed modified versions, keep derivative works under AGPL-3.0, and state your changes.
Contributions are accepted under the Contributor License Agreement (CLA.md) — you keep your copyright and grant the rights that make the dual-licensing model possible.
See LICENSING.md for the full model and CONTRIBUTING for how to contribute.
Need help with deployment, customization, or training? Contact us at [email protected]
Built with ❤️ for the manufacturing community
Made by manufacturers, for manufacturers
⭐ If you find OpenMES useful, please give it a star!