Saleman WMS
The complete Smart Warehouse Management System for Uzum and Yandex marketplace sellers. Manage your inventory in real-time, visualize your warehouse in 3D, sync orders automatically, and control every role in your team.
Saleman is a full-stack web application that runs entirely in the browser. It uses Firebase Firestore for persistent cloud storage, Firebase Auth for secure login, and the Uzum Seller API (routed through a Netlify proxy to avoid CORS) for marketplace synchronization.
Real-time Dashboard
Live KPIs, customizable widgets, Charts.js graphs, AI insights, and store-level filtering.
3D Warehouse Viewer
Drag-and-drop rack placement using Three.js. Full undo/redo stack, marquee selection, and AI layout commands.
Marketplace Sync
One-click sync of products, orders, invoices, and reviews from connected Uzum accounts.
Worker Management
Role-based access control. Create worker accounts, set permissions, and manage remote sessions.
Print Center
Generate and print barcodes and product labels with QR code support for picked items.
Multi-Language
Full UI translation in English, Uzbek (O'zbek), and Russian (Русский).
Get the app running locally in under 2 minutes using Netlify Dev (required for the Uzum API proxy).
Install Netlify CLI
Required to run the /uzum-proxy redirect locally. Without it, all Uzum API calls will fail with CORS errors.
Clone and open the project
The project is a single-page application. No build step. Open the folder in VS Code.
Run netlify dev
Starts a local dev server at http://localhost:8888 which activates the proxy. The app auto-detects whether it is on localhost and routes Uzum API calls accordingly.
Log in and connect Uzum
Create a Firebase account via the register page. Go to Integrations → Connect New Store and paste your Bearer API key from Uzum Seller Panel.
Sync products
Click the Sync Uzum button in the Products tab to pull all SKUs and stock levels from your connected Uzum shops into Firestore.
localhost and routes to http://localhost:8888/uzum-proxy. On production (saleman.uz), it routes to /uzum-proxy via Netlify redirects.Authentication is handled entirely by Firebase Auth. The app supports email/password, Google Sign-In, and a special username-based login path for worker accounts.
Email / Password
Standard Firebase Auth flow with email verification required before app access.
Google Sign-In
One-click OAuth via firebase.auth.GoogleAuthProvider. Skips email verification.
Worker Username Login
Workers log in using a short username (e.g. john.doe). The app looks up their email in the public workerUsernames Firestore collection and then authenticates via Firebase.
localStorage as warevis_worker_login_pending. A new tab is opened and auth.js picks up the token in onAuthStateChanged, signs in the worker, then deletes the token.| Path | Purpose |
|---|---|
users/{workerUid} | Worker's own profile — readable by the worker. Contains isWorker:true, managerId, role. |
users/{managerId}/workers/{workerUid} | Required by Firestore security rules. Document ID must be the Auth UID. |
workerUsernames/{username} | Public index used for username → email lookup during login. Contains only email + managerId, no passwords. |
Every user session has a role property. The RBAC system controls which navigation tabs are visible, which actions are available, and whether Uzum API calls are triggered. RBAC is enforced twice: immediately on login and again 800 ms later to catch any async renders that tried to add hidden elements back.
| Role | Dashboard | Products | Orders | Finance | Workers | Integrations | Racks 3D |
|---|---|---|---|---|---|---|---|
| Manager | |||||||
| Warehouse Worker | |||||||
| Accountant | |||||||
| Logistics / Driver | |||||||
| Print Manager |
currentUser.isWorker === true, initializeApplication() skips loadWorkers(), loadUzumAccounts(), and all Uzum API calls entirely. Workers only receive their own role-limited product data via getUserProductsRef().The dashboard is the home screen for managers. It displays a fully customizable grid of widgets showing live KPIs, charts, and summaries. The layout is persisted to Firestore and remembered between sessions.
| Widget ID | Name | Category | Description |
|---|---|---|---|
kpi-total-products | Total Products | Inventory | Count of all products in Firestore. |
kpi-low-stock | Low Stock Items | Inventory | Products with stock below the threshold. |
kpi-orders-today | Orders Today | Orders | Number of orders placed today. |
revenue-chart | Revenue Over Time | Financial | Line chart of revenue from synced Uzum orders. |
top-products | Top 10 Products | Sales | Bar chart of best-selling products by revenue. Filterable by Uzum account. |
store-sales-bar | Store Sales | Sales | Grouped bar chart comparing sales across connected Uzum stores. |
revenue-forecast | Stock by Category | Inventory | Horizontal bar chart (Chart.js) of stock levels grouped by category. Uses quantityFbs as the primary stock field. |
api-status | API Status | System | Shows which Uzum accounts are connected and their shop list. |
notes | Notes | Utility | Freeform text notes saved to Firestore per user. |
ai-insight | AI Insights | AI | GPT-powered text summary of inventory status and recommendations. |
users/{uid}/dashboardLayout in Firestore.The Products tab is the central inventory view. It supports local products added manually, and Uzum products synced from the marketplace API. Both product types are stored in separate Firestore sub-collections and rendered in separate table views.
Configurable Columns
Managers can show/hide columns via the Column Settings modal. Preferences are saved to Firestore.
Uzum Products View
Expandable rows show SKU-level data including price, FBS stock, images (540px high-res), status badges, and promo tags.
SKU Details Panel
Full details panel per SKU: barcode, IKPU, all stock quantities (Active, FBS, Sold, Returned, etc.), dimensions, rating, commissions, and blocking reasons.
Archived Toggle
Show/hide archived products. Archived products are filtered from the main view by default.
Search & Filter
Real-time search by name, SKU ID, or barcode. Filter by category, status, and Uzum account.
Sync Uzum
Fetches all shops for each connected API key, then imports all products and SKUs into Firestore's products_uzum collection. Stale locks and blacklists are cleared before each sync.
All product images use the Uzum CDN with the t_product_540_high.jpg suffix for full resolution. Clicking any image opens a full-screen lightbox (z-index: 999999, always rendering above all other panels).
const highRes = base + '/t_product_540_high.jpg';
quantityFbs ?? p.fbs ?? quantityActive ?? quantity. This is because quantityActive is often 0 for Uzum FBS sellers even when FBS stock exists.The Stock tab provides a search-driven interface for updating inventory levels manually. Results appear as cards as you type — enter a product name, SKU, or barcode to find and adjust quantities.
| Function | Description |
|---|---|
window.renderStockSearch(query) | Called on every keypress in the stock search input. Filters local products and renders result cards. |
window.loadProducts() | Loads all products from Firestore products_local collection into window.products. |
window.saveStockChange(id, qty) | Writes the updated quantity to Firestore and logs the change to the activity log. |
The Orders tab shows all orders pulled from Uzum. Orders are organized by fulfilment type and support pagination.
All Orders
Full order list across all connected Uzum accounts, newest first.
FBO Orders
Fulfilled-by-Operator orders stored in Uzum's warehouse. Filter to see only FBO fulfilment.
Returns
Returned orders with reason codes, amount, and return date.
Pagination
Configurable rows-per-page (default 20). Page buttons are rendered dynamically. Resets to page 1 on each new sync.
| Function | Description |
|---|---|
window.syncOrders() | Fetches all orders from the Uzum API for each connected account and caches them. |
window.syncReturns() | Fetches return orders specifically. |
window.switchOrderTab(tab) | Switches between 'all', 'FBO', 'returns' views. |
window.changeOrdersPage(n) | Navigates to page n. Re-slices the cached order array. |
window.updateOrdersPerPage(n) | Updates rows per page and re-renders from page 1. |
window.renderOrdersPagination(total) | Injects page number buttons into #ordersPaginationButtons. |
The Schedule tab lets you plan and record product deliveries by vehicle. Select a vehicle, product, quantity, and date/time to create a delivery entry. Scheduled deliveries appear in a timeline view.
window.vehicles and injected into the schedule form dropdown.The Recommendations module will surface AI-driven and rule-based suggestions for restocking, promotions, and warehouse optimization. This feature is currently in active development.
The Racks tab is where you visualize and configure your physical warehouse in 3D. A 5-step setup wizard guides you through defining the area, assigning zones, boxes, and creating/placing racks.
Area — Define Warehouse Size
Enter your warehouse dimensions in square meters (50–150 m² small, 150–500 m² medium, 500+ m² large). The 3D floor grid is scaled to match.
Assign — Zone Configuration
Define named zones within the warehouse (e.g. Inbound, Outbound, Storage). Used for product-to-zone assignment later.
Boxes — Box Type Setup
Configure box/bin types with size presets for accurate rack capacity calculations.
Racks — Rack Template Editor
Set shelf counts, dimensions, and visual color palette for rack types. A live 3D miniature preview renders in the side panel.
Launch — Generate 3D Scene
Generates the full Three.js warehouse scene. The layout is persisted to Firestore under users/{uid}/warehouseLayout.
vision.js is the "Eyes" module of Saleman — it owns all Three.js scene logic. It is loaded as an ES module and exports key functions used by app.js and the rack setup wizard.
Drag & Drop Racks
Racks can be freely dragged on the warehouse floor. Collision detection prevents overlapping placements.
Undo / Redo Stack
Full undo/redo history for all rack moves, additions, and deletions. Supports Ctrl+Z / Ctrl+Y keyboard shortcuts.
Multi-Select & Marquee
Hold Ctrl and click to add racks to selection. Draw a rectangle to marquee-select. Move selected racks as a group.
Grid Picker
Enters a crosshair mode to precisely place new racks at a grid-snapped position by clicking the floor.
Color Palette
Rack metal, shelf outer, shelf inner, and floor colors are configurable. Defaults are set to indigo metal with orange shelves.
Fullscreen Mode
The 3D viewport can expand to fill the browser window with a floating toolbar, close button, and maximize toggle.
| Variable | Type | Purpose |
|---|---|---|
scene | THREE.Scene | Main warehouse scene (viewer) |
previewScene | THREE.Scene | Isolated rack preview (Setup Wizard Step 4) |
camera | THREE.PerspectiveCamera | Main scene camera with OrbitControls |
renderer | THREE.WebGLRenderer | Main WebGL renderer — anti-aliasing enabled |
undoStack / redoStack | Array<Action> | Undo/redo history for rack operations |
RACK_COLORS | window object | Current active color palette applied to all newly created racks |
3dmodels.js provides procedurally generated Three.js meshes for warehouse structures. All textures are created in-memory using Canvas API — no external image files needed.
Warehouse Door
Metal-look door with horizontal slat texture and caution stripe at the base. Procedurally generated texture (512×512 Canvas). Exposed as window.createWarehouseDoor(x,y,z,scene).
Storage Racks
Multi-shelf rack meshes with customizable shelf count, width, depth, and height. Metal uprights and shelf panels rendered as BoxGeometries.
Floor Grid
Dynamic grid helper sized to the warehouse area. Grid scale responds to the area input in the Setup Wizard.
Product Boxes
Small box meshes placed on rack shelves to represent stocked items. Togglable via setBoxesVisibility().
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 512;
// 1. Base metal colour (grey)
// 2. Horizontal slat shadows every 25 px
// 3. Yellow + black caution stripes at bottom 80 px
return new THREE.CanvasTexture(canvas);
}
Managers can type natural-language commands into the AI Design prompt within the Racks view. The AI interprets the command and rearranges, adds, or removes racks in the 3D scene automatically.
| Function | Description |
|---|---|
applyAIDesignCommand(cmd) | Parses the command string and applies corresponding Three.js scene mutations. Uses the undo stack so actions can be reversed. |
showAILoading() | Displays a loading overlay while the AI command is being processed. |
hideAILoading() | Hides the loading overlay on completion or error. |
The Integrations tab lists all connected Uzum seller accounts. Each account is identified by a Bearer API key. Multiple accounts (shops) can be connected simultaneously.
Get your API key
Log in to the Uzum Seller Portal → Account Settings → API Key. Copy the Bearer token.
Click "Connect New Store"
In the Integrations tab, click the button and paste your Bearer token. The system calls fetchUzumShops() to verify the key and retrieve the shop list.
Save and sync
The account is stored in users/{uid}/uzumAccounts. Navigate to Products → Sync Uzum to import all SKUs.
All Uzum API calls are routed through a Netlify redirect to avoid browser CORS restrictions. The proxy base URL is determined at runtime.
const h = window.location.hostname;
if (h === 'localhost' || h === '127.0.0.1')
return 'http://localhost:8888/uzum-proxy';
return '/uzum-proxy';
};
fetchUzumShops().importUzumProducts(key).syncOrders().fetchUzumReviews(apiKey).window.__uzumSyncInProgress, window.__uzumShopScanLocks, window.uzumBlacklist, and the uzum_blacklist localStorage key. This ensures stale locks from a previous crashed sync never silently block subsequent syncs.The Invoice tab shows Uzum fulfilment invoices (internally called "timeslots"). They are fetched automatically at startup after accounts load, and can be viewed, filtered, and exported.
timeslots in their allowed pages list.The Finance tab provides a financial overview of revenue, expenses, and profit margins based on order and invoice data synced from Uzum.
The Reviews tab fetches product reviews from Uzum and displays them with ratings, customer messages, and dates. Requires a connected Uzum API key.
The Print Center generates printable barcode labels for your products. Two tabs: Products (label by product) and Invoices (label by invoice/order).
QR Code Support
Each label includes a QR code generated by the qrcodejs library, linking to the product record.
PDF Export
Labels are rendered in a print-ready layout. Clicking Print opens the browser print dialog with a pre-formatted page.
Invoice Labels
Generate labels for Uzum fulfilment invoices with item details, quantity, and destination.
| Function | Description |
|---|---|
window.renderLabelProducts() | Renders product label cards in the Print Center. |
window.renderInvoiceList() | Renders invoice-based labels. |
window.switchLabelTab(tabName) | Switches between 'products' and 'invoices' sub-tabs. |
window.printLabel(productId) | Opens the browser print dialog for a single product label. |
Every stock change, sync event, and significant user action is written to the Logs tab. Logs are retained according to the configured retention duration and can be cleared manually.
| Setting | Options |
|---|---|
| Retention Duration | 1 Week · 1 Month (default) · 3 Months · 6 Months · 1 Year · Unlimited |
| Clear Expired Logs | Removes log entries older than the retention window from Firestore. |
| Clear All Logs | Wipes all log entries for the current user. |
Managers can create sub-accounts for warehouse workers. Each worker gets a Firebase Auth account, a username for login, and a role that controls their UI permissions.
Click "+ Add User"
Opens the Worker Wizard modal.
Enter name and select role
Choose from: Warehouse Worker, Picker, Packer, Logistics, Driver, Finance, Accountant, Print Manager, or Manager.
Generate credentials
Click "Generate". A clean username (firstname.001) and 6-character password are generated and displayed.
Save
Creates a real Firebase Auth account using a secondary app instance (the manager session is never interrupted). Three Firestore documents are written. Email + password are stored in the worker sub-doc for Login-as and Reset Password operations.
| Action | Description |
|---|---|
| Login | Writes a one-time token to localStorage and opens the app in a new tab. The worker is signed in there without touching the manager's current session. |
| Reset Password | Generates a new random 8-character password, signs in as the worker via a secondary Firebase App, calls updatePassword(), and saves the new password back to Firestore. |
| Delete Account | Deletes the Firebase Auth account, the manager's workers sub-doc, the top-level users/{uid} profile, and the workerUsernames/{username} lookup doc. |
The Settings page covers general preferences, stock log retention, dashboard layout editor, AI feature toggles, and system information.
| Section | Controls |
|---|---|
| General | Language selector (English / O'zbek / Русский) |
| Stock Log Retention | Dropdown (1 week → unlimited). "Clear Expired Logs" button. |
| Dashboard | "Edit Dashboard" button — opens the widget reorder/visibility modal |
| AI Features | Toggle to enable/disable AI dashboard insights panel |
| System Info | Live counts: Total Products, Total Vehicles, Total Logs, App Version |
| Actions | Export Data (JSON download), Clear All Logs |
Saleman's UI is fully translatable. Language is selected in Settings and persisted to Firestore. The app uses data-lang-key HTML attributes to identify translatable elements.
| Language | Code | Coverage |
|---|---|---|
| 🇬🇧 English | en | Full — primary language |
| 🇺🇿 O'zbek | uz | Full — all core UI elements |
| 🇷🇺 Русский | ru | Full — all core UI elements |
Saleman uses Firebase Firestore (with offline persistence) for all data storage, and Firebase Auth for user management.
| Path | Contents | Owner |
|---|---|---|
users/{uid} | Manager profile: name, warehouseName, profilePic, uzumAccounts, dashboardLayout, language | Manager |
users/{uid}/products_local | Manually added warehouse products | Manager |
users/{uid}/products_uzum | Synced Uzum SKUs with full stock data | Manager |
users/{uid}/products_yandex | Yandex Market products (future) | Manager |
users/{uid}/workers/{workerUid} | Worker profile reference required by security rules | Manager |
users/{uid}/warehouseLayout | 3D warehouse config: area, zones, rack positions | Manager |
users/{uid}/dashboardLayout | Widget order and visibility preferences | Manager |
users/{workerUid} | Worker profile: isWorker, role, managerId, username | Worker |
workerUsernames/{username} | Public lookup index: username → email + managerId | Global |
enablePersistence({ synchronizeTabs: true }). On newer Firebase SDK versions, the persistentLocalCache with persistentMultipleTabManager is used instead.All functions exposed on window by app.js. These can be called from the browser console or from other modules.
The netlify.toml file defines a redirect rule that proxies all /uzum-proxy/* requests to the Uzum Seller API, injecting the correct Host header and bypassing browser CORS restrictions.
from = "/uzum-proxy/*"
to = "https://api-seller.uzum.uz/api/seller-openapi/:splat"
status = 200
force = true
:splat wildcard passes the remainder of the path to Uzum. For example, /uzum-proxy/seller/shop becomes https://api-seller.uzum.uz/api/seller-openapi/seller/shop.Saleman is deployed on Netlify at saleman.uz. The proxy redirects, custom domain, and HTTPS are all handled by Netlify's platform.
Netlify Hosting
Static file hosting with CDN edge delivery. Deploy by pushing to the connected Git branch.
Firebase Project
Project ID: salemanwms. Firestore rules restrict data access to authenticated users and their workers via the isManager() check.
HTTPS & Custom Domain
Netlify auto-provisions an SSL certificate for saleman.uz and all docs.saleman.uz subdomains.
netlify dev (not live-server or direct file open) so the Uzum proxy functions correctly. The app detects localhost hostname automatically.