### TypeScript Fiscal Provider Interface for POS Systems Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Defines the interface for interacting with fiscal hardware, enabling operations like opening/closing shifts, registering sales, and processing returns. It includes detailed type definitions for parameters and responses, facilitating integration with certified online cash registers in Uzbekistan. The example demonstrates using a retry queue for robust fiscal transaction processing. ```typescript // Fiscal Provider Interface interface FiscalProvider { openShift(params: { cashierId: string; terminalId: string; initialCash: number; }): Promise; registerSale(params: { receiptId: string; items: Array<{ name: string; quantity: number; price: number; taxRate: number; }>; payments: Array<{ type: 'cash' | 'card' | 'electronic'; amount: number; }>; customer?: { loyaltyCard?: string; phone?: string; }; }): Promise; registerReturn(params: { originalReceiptId: string; items: Array<{ name: string; quantity: number; price: number; }>; }): Promise; closeShift(params: { cashierId: string; actualCash: number; }): Promise; } interface FiscalResponse { success: boolean; fiscalDocNumber?: string; fiscalSign?: string; qrCode?: string; errorCode?: string; errorMessage?: string; } // Example usage with retry queue import { FiscalQueue } from './fiscal-queue'; const fiscalQueue = new FiscalQueue({ maxRetries: 5, retryDelayMs: 3000 }); async function processSale(sale: Sale) { try { const result = await fiscalQueue.enqueue({ operation: 'registerSale', params: { receiptId: sale.id, items: sale.items.map(item => ({ name: item.product.name, quantity: item.quantity, price: item.price, taxRate: item.product.taxRate || 0 })), payments: sale.payments.map(p => ({ type: p.type, amount: p.amount })) } }); if (result.success) { await updateSaleWithFiscalData(sale.id, { fiscalDocNumber: result.fiscalDocNumber, fiscalSign: result.fiscalSign, qrCode: result.qrCode, status: 'fiscalized' }); } } catch (error) { // Queue for retry when printer comes back online console.error('Fiscal error, will retry:', error); } } ``` -------------------------------- ### Implement POS Checkout Workflow (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Implements the `POSCheckout` class in TypeScript, handling the complete sale workflow. This includes starting a new receipt, scanning items via barcode, adding items, applying discounts (percentage, fixed, and loyalty-based), processing payments, and completing the sale. It interacts with local databases and external APIs for product information and loyalty programs. ```typescript // Complete sale workflow in POS class POSCheckout { private currentReceipt: SaleReceipt | null = null; async startNewReceipt(terminalId: string, cashierId: string) { this.currentReceipt = { id: generateUUID(), receiptNumber: await this.getNextReceiptNumber(), date: new Date(), cashierId, terminalId, storeId: await this.getStoreIdForTerminal(terminalId), items: [], payments: [], subtotal: 0, totalDiscount: 0, totalTax: 0, finalAmount: 0, status: 'draft' }; } async scanBarcode(barcode: string) { // Find SKU by barcode const sku = await this.localDB.query( 'SELECT * FROM product_variants WHERE barcodes LIKE ?', [`%${barcode}%`] ); if (sku.length === 0) { throw new Error('Product not found'); } await this.addItem(sku[0].id, 1); } async addItem(skuId: string, quantity: number) { const product = await this.getProduct(skuId); // Check stock availability const stock = await this.getStock(skuId); if (stock < quantity && !this.allowNegativeStock) { throw new Error('Insufficient stock'); } const existingItem = this.currentReceipt!.items.find(i => i.skuId === skuId); if (existingItem) { existingItem.quantity += quantity; existingItem.lineTotal = existingItem.quantity * existingItem.price; } else { this.currentReceipt!.items.push({ skuId, productName: product.name, quantity, price: product.price, discount: 0, taxRate: product.taxRate, lineTotal: quantity * product.price }); } this.calculateTotals(); } async applyDiscount(type: 'percentage' | 'fixed', value: number, itemIndex?: number) { if (itemIndex !== undefined) { // Item-level discount const item = this.currentReceipt!.items[itemIndex]; if (type === 'percentage') { item.discount = item.price * item.quantity * (value / 100); } else { item.discount = value; } item.lineTotal = item.price * item.quantity - item.discount; } else { // Receipt-level discount const subtotal = this.currentReceipt!.subtotal; const discountAmount = type === 'percentage' ? subtotal * (value / 100) : value; // Distribute proportionally this.currentReceipt!.items.forEach(item => { const proportion = item.lineTotal / subtotal; item.discount = discountAmount * proportion; item.lineTotal = item.price * item.quantity - item.discount; }); } this.calculateTotals(); } async applyLoyaltyDiscount(customerPhone: string) { // Call JOWi Club API const customer = await fetch(`https://api.jowiclub.uz/customers/${customerPhone}`, { headers: { 'X-API-Key': this.loyaltyApiKey } }).then(r => r.json()); if (customer.discountPercent > 0) { await this.applyDiscount('percentage', customer.discountPercent); this.currentReceipt!.customerId = customer.id; } return customer; } async addPayment(type: Payment['type'], amount: number) { this.currentReceipt!.payments.push({ type, amount }); const totalPaid = this.currentReceipt!.payments.reduce((sum, p) => sum + p.amount, 0); if (totalPaid >= this.currentReceipt!.finalAmount) { await this.completeSale(); } } private calculateTotals() { this.currentReceipt!.subtotal = this.currentReceipt!.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); this.currentReceipt!.totalDiscount = this.currentReceipt!.items.reduce( (sum, item) => sum + item.discount, 0 ); this.currentReceipt!.totalTax = this.currentReceipt!.items.reduce( (sum, item) => { const taxableAmount = item.lineTotal; return sum + (taxableAmount * item.taxRate / 100); }, 0 ); this.currentReceipt!.finalAmount = this.currentReceipt!.subtotal - this.currentReceipt!.totalDiscount; } private async completeSale() { // 1. Save to local DB ``` -------------------------------- ### GET /api/inventory/stock Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves current stock levels for products in the inventory, with optional filtering. ```APIDOC ## GET /api/inventory/stock ### Description Retrieves current stock levels for products in the inventory, with optional filtering. ### Method GET ### Endpoint /api/inventory/stock ### Parameters #### Query Parameters - **warehouseId** (string) - Optional - Filters stock levels for a specific warehouse. - **categoryId** (string) - Optional - Filters stock levels for products within a specific category. - **lowStockOnly** (boolean) - Optional - If true, only returns products with stock levels below their minimum threshold. ### Response #### Success Response (200 OK) - **stock** (array) - An array of stock level objects. - **skuId** (string) - The SKU ID of the product. - **productName** (string) - The name of the product. - **quantity** (number) - The current quantity in stock. - **minLevel** (number) - The minimum stock level threshold. - **warehouse** (string) - The name or ID of the warehouse. #### Response Example ```json [ { "skuId": "sku-xyz", "productName": "Example Widget", "quantity": 50, "minLevel": 10, "warehouse": "Main Warehouse" }, { "skuId": "sku-abc", "productName": "Another Gadget", "quantity": 5, "minLevel": 20, "warehouse": "Main Warehouse" } ] ``` ``` -------------------------------- ### GET /api/reports/top-products Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves a list of top-selling products within a specified period. Results can be limited and filtered by store. ```APIDOC ## GET /api/reports/top-products ### Description Retrieves a list of top-selling products within a specified period. Results can be limited and filtered by store. ### Method GET ### Endpoint `/api/reports/top-products` ### Query Parameters - **startDate** (Date) - Required - The start date for the report. - **endDate** (Date) - Required - The end date for the report. - **storeId** (string) - Optional - Filter reports for a specific store. - **limit** (number) - Optional - The maximum number of products to return. Defaults to 20. ### Request Example ``` GET /api/reports/top-products?startDate=2025-01-15T00:00:00Z&endDate=2025-01-16T23:59:59Z&limit=10 ``` ### Response #### Success Response (200) - An array of product objects, each containing: - **skuId** (string) - The product's SKU ID. - **productName** (string) - The name of the product. - **category** (string) - The category the product belongs to. - **quantitySold** (number) - The total quantity sold. - **revenue** (number) - The total revenue generated by the product. - **averagePrice** (number) - The average selling price. #### Response Example ```json [ { "skuId": "sku_123", "productName": "T-Shirt Red XL", "category": "Clothing", "quantitySold": 156, "revenue": 2340000, "averagePrice": 15000 } ] ``` ``` -------------------------------- ### Search Products API Call with Filters (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Implements a function to search for products based on a query string and various filters like category, stock availability, and price range. It constructs a URL with query parameters and sends a GET request to '/api/products/search', requiring JWT authorization. The function returns paginated product data. ```typescript // Search products with filters async function searchProducts(query: string, filters: { categoryId?: string; inStock?: boolean; minPrice?: number; maxPrice?: number; }) { const params = new URLSearchParams({ q: query, ...filters }); const response = await fetch(`/api/products/search?${params}`, { headers: { 'Authorization': `Bearer ${jwt}` } }); const data = await response.json(); // Returns: { items: Product[], total: number, page: number } return data; } ``` -------------------------------- ### GET /api/reports/employee-performance Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves performance metrics for employees within a given date range, filterable by store. ```APIDOC ## GET /api/reports/employee-performance ### Description Retrieves performance metrics for employees within a given date range, filterable by store. ### Method GET ### Endpoint `/api/reports/employee-performance` ### Query Parameters - **startDate** (Date) - Required - The start date for the report. - **endDate** (Date) - Required - The end date for the report. - **storeId** (string) - Optional - Filter reports for a specific store. ### Request Example ``` GET /api/reports/employee-performance?startDate=2025-01-15T00:00:00Z&endDate=2025-01-16T23:59:59Z&storeId=store_abc ``` ### Response #### Success Response (200) - An array of employee performance objects, each containing: - **employeeId** (string) - The unique ID of the employee. - **name** (string) - The name of the employee. - **receiptsProcessed** (number) - The number of receipts processed by the employee. - **totalRevenue** (number) - The total revenue generated by the employee. - **averageReceipt** (number) - The average receipt value for the employee. - **shiftsWorked** (number) - The number of shifts worked. - **averageReceiptsPerShift** (number) - Average receipts processed per shift. #### Response Example ```json [ { "employeeId": "emp_001", "name": "Aziza Karimova", "receiptsProcessed": 145, "totalRevenue": 6525000, "averageReceipt": 45000, "shiftsWorked": 12, "averageReceiptsPerShift": 12 } ] ``` ``` -------------------------------- ### GET /api/reports/profit-loss Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves the profit and loss statement for a specified period, with optional store filtering. ```APIDOC ## GET /api/reports/profit-loss ### Description Retrieves the profit and loss statement for a specified period, with optional store filtering. ### Method GET ### Endpoint `/api/reports/profit-loss` ### Query Parameters - **startDate** (Date) - Required - The start date for the report. - **endDate** (Date) - Required - The end date for the report. - **storeId** (string) - Optional - Filter reports for a specific store. ### Request Example ``` GET /api/reports/profit-loss?startDate=2025-01-01T00:00:00Z&endDate=2025-01-31T23:59:59Z&storeId=store_xyz ``` ### Response #### Success Response (200) - An object containing financial summary data: - **revenue** (number) - Total revenue. - **cogs** (number) - Cost of Goods Sold. - **grossProfit** (number) - Gross Profit (Revenue - COGS). - **grossMargin** (number) - Gross Margin (Gross Profit / Revenue). - **returns** (number) - Total value of returns. - **writeOffs** (number) - Total value of write-offs. - **netProfit** (number) - Net Profit. - **profitMargin** (number) - Profit Margin (Net Profit / Revenue). #### Response Example ```json { "revenue": 15750000, "cogs": 9450000, "grossProfit": 6300000, "grossMargin": 0.40, "returns": 275000, "writeOffs": 125000, "netProfit": 5900000, "profitMargin": 0.37 } ``` ``` -------------------------------- ### GET /api/reports/hourly-sales Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves sales data broken down by hour for a specific date. Can be filtered by store. ```APIDOC ## GET /api/reports/hourly-sales ### Description Retrieves sales data broken down by hour for a specific date. Can be filtered by store. ### Method GET ### Endpoint `/api/reports/hourly-sales` ### Query Parameters - **date** (Date) - Required - The specific date for the hourly sales report. - **storeId** (string) - Optional - Filter reports for a specific store. ### Request Example ``` GET /api/reports/hourly-sales?date=2025-01-16&storeId=store_abc ``` ### Response #### Success Response (200) - An array of objects, each representing sales for a specific hour: - **hour** (number) - The hour of the day (0-23). - **receipts** (number) - The number of receipts during that hour. - **revenue** (number) - The total revenue during that hour. #### Response Example ```json [ { "hour": 9, "receipts": 5, "revenue": 225000 }, { "hour": 10, "receipts": 12, "revenue": 540000 }, { "hour": 11, "receipts": 18, "revenue": 810000 } ] ``` ``` -------------------------------- ### GET /api/reports/sales-by-period Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves sales data aggregated by a specified time period. Supports filtering by store, category, and cashier. ```APIDOC ## GET /api/reports/sales-by-period ### Description Retrieves sales data aggregated by a specified time period. Supports filtering by store, category, and cashier. ### Method GET ### Endpoint `/api/reports/sales-by-period` ### Query Parameters - **startDate** (Date) - Required - The start date for the report. - **endDate** (Date) - Required - The end date for the report. - **storeId** (string) - Optional - Filter reports for a specific store. - **categoryId** (string) - Optional - Filter reports for a specific product category. ### Request Example ``` GET /api/reports/sales-by-period?startDate=2025-01-15T00:00:00Z&endDate=2025-01-16T23:59:59Z&storeId=store_abc ``` ### Response #### Success Response (200) - **summary** (object) - Contains aggregated sales summary data. - **totalRevenue** (number) - Total revenue for the period. - **totalReceipts** (number) - Total number of receipts. - **averageReceipt** (number) - Average receipt value. - **totalReturns** (number) - Total value of returns. - **netRevenue** (number) - Net revenue after returns. - **daily** (array) - An array of daily sales data. - **date** (string) - The date of the daily report. - **revenue** (number) - Revenue for the day. - **receipts** (number) - Number of receipts for the day. - **returns** (number) - Value of returns for the day. #### Response Example ```json { "summary": { "totalRevenue": 15750000, "totalReceipts": 342, "averageReceipt": 46052, "totalReturns": 275000, "netRevenue": 15475000 }, "daily": [ { "date": "2025-01-15", "revenue": 850000, "receipts": 23, "returns": 15000 }, { "date": "2025-01-16", "revenue": 920000, "receipts": 28, "returns": 0 } ] } ``` ``` -------------------------------- ### Create Product with Variants API Call (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Demonstrates how to create a new product with its associated variants using a POST request to the '/api/products' endpoint. It requires a JWT for authorization and sends product details including name, category, and variant SKUs in JSON format. The function returns the API response. ```typescript // Create product with variants async function createProductWithVariants() { const response = await fetch('/api/products', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jwt}` }, body: JSON.stringify({ name: 'T-Shirt Classic', categoryId: 'cat_clothing', unitOfMeasure: 'pcs', taxRate: 12, variants: [ { sku: 'TSHIRT-RED-S', attributes: { color: 'Red', size: 'S' }, barcodes: ['4607001234567'] }, { sku: 'TSHIRT-RED-M', attributes: { color: 'Red', size: 'M' }, barcodes: ['4607001234574'] }, { sku: 'TSHIRT-BLUE-S', attributes: { color: 'Blue', size: 'S' }, barcodes: ['4607001234581'] } ] }) }); return await response.json(); } ``` -------------------------------- ### Database Insert and Update Operations (JavaScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Demonstrates inserting new records into 'receipts' and 'outbox' tables, and updating 'receipts' with fiscal data and completion status. Uses asynchronous operations with `await`. ```javascript await this.localDB.insert('receipts', this.currentReceipt); // 2. Add to sync outbox await this.localDB.insert('outbox', { entityType: 'sale', entityId: this.currentReceipt!.id, operation: 'insert', payload: this.currentReceipt, syncStatus: 'pending' }); await this.localDB.update('receipts', { id: this.currentReceipt!.id }, { fiscalData: this.currentReceipt!.fiscalData, status: 'completed' } ); ``` -------------------------------- ### Fiscalization Process (JavaScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Handles the fiscalization of a sale by registering it with a fiscal provider and updating the receipt with fiscal data upon success. Includes conditional logic for processing the fiscalization result. ```javascript // 3. Fiscalize const fiscalResult = await this.fiscalProvider.registerSale({ receiptId: this.currentReceipt!.id, items: this.currentReceipt!.items, payments: this.currentReceipt!.payments }); if (fiscalResult.success) { this.currentReceipt!.fiscalData = { docNumber: fiscalResult.fiscalDocNumber!, fiscalSign: fiscalResult.fiscalSign!, qrCode: fiscalResult.qrCode! }; this.currentReceipt!.status = 'completed'; } ``` -------------------------------- ### Fiscal Provider Interface Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Defines the interface for interacting with fiscal devices and services, including opening and closing shifts, registering sales and returns, and handling fiscal responses. ```APIDOC ## Fiscal Provider Interface ### Description This interface outlines the contract for fiscal providers, enabling the JOWi Shop system to interact with various certified online cash registers in Uzbekistan. It supports core operations like opening and closing shifts, registering sales and returns, and retrieving fiscalization details. ### Methods - **openShift**: Opens a new fiscal shift. - **registerSale**: Registers a sales transaction. - **registerReturn**: Registers a sales return. - **closeShift**: Closes the current fiscal shift. ### Interfaces #### `FiscalProvider` ```typescript interface FiscalProvider { openShift(params: { cashierId: string; terminalId: string; initialCash: number; }): Promise; registerSale(params: { receiptId: string; items: Array<{ name: string; quantity: number; price: number; taxRate: number }>; payments: Array<{ type: 'cash' | 'card' | 'electronic'; amount: number }>; customer?: { loyaltyCard?: string; phone?: string }; }): Promise; registerReturn(params: { originalReceiptId: string; items: Array<{ name: string; quantity: number; price: number }>; }): Promise; closeShift(params: { cashierId: string; actualCash: number; }): Promise; } ``` #### `FiscalResponse` ```typescript interface FiscalResponse { success: boolean; fiscalDocNumber?: string; fiscalSign?: string; qrCode?: string; errorCode?: string; errorMessage?: string; } ``` ### Example Usage (with retry queue) ```typescript import { FiscalQueue } from './fiscal-queue'; const fiscalQueue = new FiscalQueue({ maxRetries: 5, retryDelayMs: 3000 }); async function processSale(sale: Sale) { try { const result = await fiscalQueue.enqueue({ operation: 'registerSale', params: { receiptId: sale.id, items: sale.items.map(item => ({ name: item.product.name, quantity: item.quantity, price: item.price, taxRate: item.product.taxRate || 0 })), payments: sale.payments.map(p => ({ type: p.type, amount: p.amount })) } }); if (result.success) { await updateSaleWithFiscalData(sale.id, { fiscalDocNumber: result.fiscalDocNumber, fiscalSign: result.fiscalSign, qrCode: result.qrCode, status: 'fiscalized' }); } } catch (error) { console.error('Fiscal error, will retry:', error); } } ``` ``` -------------------------------- ### GET /api/reports/export Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Exports a specified report type to CSV format. Supports filtering by date range and store. ```APIDOC ## GET /api/reports/export ### Description Exports a specified report type to CSV format. Supports filtering by date range and store. ### Method GET ### Endpoint `/api/reports/export` ### Query Parameters - **type** (string) - Required - The type of report to export (e.g., 'sales', 'products'). - **startDate** (Date) - Required - The start date for the report. - **endDate** (Date) - Required - The end date for the report. - **storeId** (string) - Optional - Filter reports for a specific store. ### Request Example ``` GET /api/reports/export?type=sales-by-period&startDate=2025-01-01&endDate=2025-01-31&storeId=store_abc ``` ### Response #### Success Response (200) (CSV File) - Returns a CSV file containing the requested report data. #### Response Example A CSV file download will be initiated. The filename will be in the format `[reportType]_[timestamp].csv`. ``` -------------------------------- ### POST /api/inventory/counts Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Creates a new inventory count document to initiate an inventory count process. ```APIDOC ## POST /api/inventory/counts ### Description Creates a new inventory count document to initiate an inventory count process. ### Method POST ### Endpoint /api/inventory/counts ### Parameters #### Request Body - **warehouseId** (string) - Required - The ID of the warehouse for which the count is being performed. - **date** (Date) - Optional - The date of the inventory count. Defaults to the current date if not provided. ### Response #### Success Response (201 Created) - **id** (string) - The unique identifier of the created inventory count document. - **documentNumber** (string) - The document number for the count. - **date** (Date) - The date of the count. - **warehouseId** (string) - The ID of the warehouse. - **responsibleUserId** (string) - The ID of the user responsible for the count. - **status** (string) - The status of the count ('in_progress' or 'completed'). #### Response Example ```json { "id": "count-789", "documentNumber": "INVCOUNT-001", "date": "2023-10-27T11:00:00Z", "warehouseId": "wh-abc", "responsibleUserId": "user-567", "status": "in_progress" } ``` ``` -------------------------------- ### JOWi Club API Client (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt A TypeScript client for interacting with the JOWi Club loyalty program API. It handles fetching customer data by phone or card, and accruing or redeeming loyalty points. Dependencies include the native `fetch` API. It returns customer details or transaction statuses. ```typescript class JOWiClubClient { private apiKey: string; private baseUrl: string = 'https://api.jowiclub.uz'; constructor(apiKey: string) { this.apiKey = apiKey; } async getCustomerByPhone(phone: string): Promise { try { const response = await fetch(`${this.baseUrl}/customers/by-phone/${phone}`, { headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' } }); if (response.status === 404) return null; return await response.json(); } catch (error) { console.error('JOWi Club API error:', error); // Graceful degradation - continue without loyalty return null; } } async getCustomerByCard(cardNumber: string): Promise { try { const response = await fetch(`${this.baseUrl}/customers/by-card/${cardNumber}`, { headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' } }); if (response.status === 404) return null; return await response.json(); } catch (error) { console.error('JOWi Club API error:', error); return null; } } async accruePoints(transaction: { customerId: string; receiptId: string; amount: number; items: Array<{ productId: string; quantity: number; amount: number }>; timestamp: Date; }): Promise { const response = await fetch(`${this.baseUrl}/transactions/accrue`, { method: 'POST', headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(transaction) }); return await response.json(); } async redeemPoints(redemption: { customerId: string; receiptId: string; points: number; timestamp: Date; }): Promise { const response = await fetch(`${this.baseUrl}/transactions/redeem`, { method: 'POST', headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify(redemption) }); return await response.json(); } } interface LoyaltyCustomer { id: string; phone: string; name: string; cardNumber: string; level: 'bronze' | 'silver' | 'gold' | 'platinum'; pointsBalance: number; discountPercent: number; birthday?: string; } interface PointsAccrualResult { success: boolean; pointsEarned: number; newBalance: number; } interface PointsRedemptionResult { success: boolean; pointsRedeemed: number; discountAmount: number; newBalance: number; } ``` -------------------------------- ### TypeScript: Get Stock Levels API Call Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Defines a TypeScript function `getStockLevels` for retrieving current inventory stock levels. It accepts optional filters for `warehouseId`, `categoryId`, and `lowStockOnly`, constructing a query parameter string for the API request to `/api/inventory/stock`. Requires JWT authorization. ```typescript async function getStockLevels(filters: { warehouseId?: string; categoryId?: string; lowStockOnly?: boolean; }) { const params = new URLSearchParams(filters as any); const response = await fetch(`/api/inventory/stock?${params}`, { headers: { 'Authorization': `Bearer ${jwt}` } }); const stock = await response.json(); // Returns: [{ skuId, productName, quantity, minLevel, warehouse }] return stock; } ``` -------------------------------- ### TypeScript Outbox/Inbox Sync Manager for Offline POS Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Defines interfaces for Outbox and Inbox entries and a SyncManager class to handle bidirectional data synchronization for POS terminals. It manages sending local changes to the server and applying server updates locally, including conflict resolution logic. Dependencies include a local SQLite database and an API client. ```typescript interface OutboxEntry { id: string; entityType: 'sale' | 'product' | 'stock' | 'shift'; entityId: string; operation: 'insert' | 'update' | 'delete'; payload: any; timestamp: Date; syncStatus: 'pending' | 'syncing' | 'synced' | 'failed'; retryCount: number; } // Inbox - Server updates to apply locally interface InboxEntry { id: string; entityType: string; entityId: string; operation: string; payload: any; serverTimestamp: Date; applied: boolean; } // Sync Manager Implementation class SyncManager { private db: SQLiteDatabase; private api: ApiClient; async syncToServer(): Promise { const pendingEntries = await this.db.query( 'SELECT * FROM outbox WHERE syncStatus = ? ORDER BY timestamp', ['pending'] ); const results: SyncResult = { success: 0, failed: 0 }; for (const entry of pendingEntries) { try { await this.db.update('outbox', { id: entry.id }, { syncStatus: 'syncing' }); const response = await this.api.post('/sync/receive', { entityType: entry.entityType, operation: entry.operation, payload: entry.payload, clientTimestamp: entry.timestamp }); if (response.success) { await this.db.update('outbox', { id: entry.id }, { syncStatus: 'synced' }); results.success++; } } catch (error) { await this.db.update('outbox', { id: entry.id }, { syncStatus: 'failed', retryCount: entry.retryCount + 1 }); results.failed++; } } return results; } async syncFromServer(): Promise { const lastSyncTime = await this.getLastSyncTimestamp(); const updates = await this.api.get('/sync/changes', { since: lastSyncTime, terminalId: this.terminalId }); for (const update of updates.changes) { await this.db.insert('inbox', { id: update.id, entityType: update.entityType, entityId: update.entityId, operation: update.operation, payload: update.payload, serverTimestamp: update.timestamp, applied: false }); } await this.applyInboxChanges(); } private async applyInboxChanges(): Promise { const changes = await this.db.query( 'SELECT * FROM inbox WHERE applied = false ORDER BY serverTimestamp' ); for (const change of changes) { // Conflict resolution: last write wins for sales, field merge for products if (change.entityType === 'product') { await this.mergeProductUpdate(change); } else { await this.applyUpdate(change); } await this.db.update('inbox', { id: change.id }, { applied: true }); } } } // Usage in POS application const syncManager = new SyncManager(localDB, apiClient); // Start background sync every 30 seconds setInterval(async () => { if (await checkInternetConnection()) { await syncManager.syncToServer(); await syncManager.syncFromServer(); } }, 30000); ``` -------------------------------- ### Complete Sale with Loyalty Points Accrual (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Function to finalize a sale and accrue loyalty points using the JOWi Club API. It takes a completed `SaleReceipt`, accrues points based on the transaction amount and items, and logs the transaction locally for reconciliation. It shows a notification of points earned. Dependencies include `localDB` and `showNotification`. ```typescript async function completeSaleWithLoyalty(receipt: SaleReceipt) { // After sale is completed and fiscalized if (receipt.customerId) { try { const result = await loyaltyClient.accruePoints({ customerId: receipt.customerId, receiptId: receipt.id, amount: receipt.finalAmount, items: receipt.items.map(item => ({ productId: item.skuId, quantity: item.quantity, amount: item.lineTotal })), timestamp: receipt.date }); // Log transaction locally for reconciliation await localDB.insert('loyalty_transactions', { receiptId: receipt.id, customerId: receipt.customerId, type: 'accrual', points: result.pointsEarned, syncStatus: 'synced' }); // Show points earned to cashier/customer showNotification(`Customer earned ${result.pointsEarned} points! New balance: ${result.newBalance}`); } catch (error) { // Log for later retry await localDB.insert('loyalty_transactions', { receiptId: receipt.id, customerId: receipt.customerId, type: 'accrual', amount: receipt.finalAmount, }); } } } ``` -------------------------------- ### Product Catalog API Interfaces (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Defines the TypeScript interfaces for `Product` and `ProductVariant` used in the Jowi Shop product catalog. These interfaces structure product data including IDs, names, categories, tax rates, and multi-dimensional variants with SKUs and attributes. No external dependencies are required. ```typescript // Product Catalog API interface Product { id: string; tenantId: string; name: string; article?: string; categoryId: string; brandId?: string; unitOfMeasure: 'pcs' | 'kg' | 'm' | 'l'; taxRate: number; // VAT percentage active: boolean; variants: ProductVariant[]; createdAt: Date; updatedAt: Date; } interface ProductVariant { id: string; productId: string; sku: string; attributes: Record; // { color: 'red', size: 'XL' } barcodes: string[]; weight?: number; lastPurchasePrice?: number; currentStock?: number; } ``` -------------------------------- ### Fetch Top Selling Products Report - TypeScript Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves a list of top-selling products within a given period and store. Supports filtering by date range and store ID, and allows specifying a limit for the number of products returned. Returns product details, quantity sold, and revenue. ```typescript async function getTopProducts(filters: ReportFilters & { limit?: number }) { const params = new URLSearchParams({ startDate: filters.startDate.toISOString(), endDate: filters.endDate.toISOString(), limit: (filters.limit || 20).toString(), ...(filters.storeId && { storeId: filters.storeId }) }); const response = await fetch(`/api/reports/top-products?${params}`, { headers: { 'Authorization': `Bearer ${jwt}` } }); return await response.json(); } ``` -------------------------------- ### Client-Side Authentication Flow (TypeScript) Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Handles user login, stores JWT tokens in local storage, and defines interfaces for authentication credentials and responses. It also includes a JWT payload interface for client-side validation. ```typescript // Authentication Flow interface AuthCredentials { email: string; password: string; } interface AuthResponse { accessToken: string; refreshToken: string; user: { id: string; email: string; name: string; tenantId: string; role: string; storeIds: string[]; }; } // Login async function login(credentials: AuthCredentials): Promise { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); if (!response.ok) { throw new Error('Invalid credentials'); } const authData = await response.json(); // Store tokens localStorage.setItem('accessToken', authData.accessToken); localStorage.setItem('refreshToken', authData.refreshToken); return authData; } // JWT Token Structure interface JWTPayload { sub: string; // user ID email: string; tenantId: string; role: string; storeIds: string[]; iat: number; exp: number; } ``` -------------------------------- ### POST /api/inventory/receipts Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Creates a new goods receipt document. This document records the incoming of goods into the inventory. ```APIDOC ## POST /api/inventory/receipts ### Description Creates a new goods receipt document. This document records the incoming of goods into the inventory. ### Method POST ### Endpoint /api/inventory/receipts ### Parameters #### Request Body - **supplierId** (string) - Required - The ID of the supplier providing the goods. - **warehouseId** (string) - Required - The ID of the warehouse where the goods are received. - **lines** (array) - Required - An array of items included in the receipt. - **skuId** (string) - Required - The SKU ID of the product. - **quantity** (number) - Required - The quantity of the product received. - **purchasePrice** (number) - Required - The purchase price per unit of the product. ### Request Example ```json { "supplierId": "supp-123", "warehouseId": "wh-abc", "lines": [ { "skuId": "sku-xyz", "quantity": 10, "purchasePrice": 5.99 } ] } ``` ### Response #### Success Response (201 Created) - **id** (string) - The unique identifier of the created goods receipt. - **documentNumber** (string) - The document number for the receipt. - **date** (Date) - The date the receipt was created. - **warehouseId** (string) - The ID of the warehouse. - **supplierId** (string) - The ID of the supplier. - **status** (string) - The status of the receipt ('draft' or 'posted'). - **lines** (array) - Details of the received items. - **skuId** (string) - The SKU ID. - **quantity** (number) - The quantity received. - **purchasePrice** (number) - The purchase price per unit. - **totalAmount** (number) - The total amount for the line item. - **totalAmount** (number) - The total amount for the entire receipt. #### Response Example ```json { "id": "receipt-456", "documentNumber": "GRN-007", "date": "2023-10-27T10:00:00Z", "warehouseId": "wh-abc", "supplierId": "supp-123", "status": "draft", "lines": [ { "skuId": "sku-xyz", "quantity": 10, "purchasePrice": 5.99, "totalAmount": 59.90 } ], "totalAmount": 59.90 } ``` ``` -------------------------------- ### POST /api/inventory/counts/{id}/complete Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Completes an inventory count, generating an adjustment document for any discrepancies. ```APIDOC ## POST /api/inventory/counts/{id}/complete ### Description Completes an inventory count, generating an adjustment document for any discrepancies. ### Method POST ### Endpoint /api/inventory/counts/{id}/complete ### Parameters #### Path Parameters - **id** (string) - Required - The ID of the inventory count document to complete. ### Response #### Success Response (200 OK) - **adjustmentDocId** (string) - The ID of the generated inventory adjustment document. - **surplus** (number) - The total quantity of surplus items. - **shortage** (number) - The total quantity of shortage items. #### Response Example ```json { "adjustmentDocId": "adj-101", "surplus": 15, "shortage": 8 } ``` ``` -------------------------------- ### Fetch Profit and Loss Report - TypeScript Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves the profit and loss statement for a given period and store. Filters include date range and an optional store ID. Returns key financial metrics such as revenue, cost of goods sold, gross profit, net profit, and profit margins. ```typescript async function getProfitLoss(filters: ReportFilters) { const params = new URLSearchParams({ startDate: filters.startDate.toISOString(), endDate: filters.endDate.toISOString(), ...(filters.storeId && { storeId: filters.storeId }) }); const response = await fetch(`/api/reports/profit-loss?${params}`, { headers: { 'Authorization': `Bearer ${jwt}` } }); return await response.json(); } ``` -------------------------------- ### Fetch Sales by Period Report - TypeScript Source: https://context7.com/timur-kayumov/jowi-shop/llms.txt Retrieves sales data for a specified period, including summary statistics and daily breakdowns. It utilizes the fetch API and requires JWT authentication. Filters can include date range, store ID, and category ID. Returns aggregated sales data. ```typescript interface ReportFilters { startDate: Date; endDate: Date; storeId?: string; categoryId?: string; cashierId?: string; } async function getSalesByPeriod(filters: ReportFilters) { const params = new URLSearchParams({ startDate: filters.startDate.toISOString(), endDate: filters.endDate.toISOString(), ...(filters.storeId && { storeId: filters.storeId }), ...(filters.categoryId && { categoryId: filters.categoryId }) }); const response = await fetch(`/api/reports/sales-by-period?${params}`, { headers: { 'Authorization': `Bearer ${jwt}` } }); return await response.json(); } ```