# Local Print Service for Windows

This service scans for label printers (Zebra, Honeywell, TSC) on the Windows client machine and provides a REST API for the web application to query and print.

## Quick Setup

### Option 1: PowerShell Script (Simple, No Installation Required)

Create a file `local-print-service.ps1`:

```powershell
# Local Print Service for XYZ POS
# Scans for label printers and provides REST API

$port = 9101
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://localhost:$port/")
$listener.Start()

Write-Host "Local Print Service running on http://localhost:$port"
Write-Host "Press Ctrl+C to stop"

# Function to get label printers
function Get-LabelPrinters {
    $printers = Get-Printer | Where-Object {
        $name = $_.Name.ToLower()
        return $name -like "*zebra*" -or 
               $name -like "*honeywell*" -or 
               $name -like "*tsc*" -or
               $name -like "*tspl*" -or
               $name -like "*intermec*" -or
               $name -like "*datamax*"
    }
    
    $result = @()
    foreach ($printer in $printers) {
        $brand = "Unknown"
        $name = $printer.Name.ToLower()
        if ($name -like "*zebra*") { $brand = "Zebra" }
        elseif ($name -like "*honeywell*") { $brand = "Honeywell" }
        elseif ($name -like "*tsc*" -or $name -like "*tspl*") { $brand = "TSC" }
        
        $result += @{
            id = $printer.Name.Replace(" ", "-").ToLower()
            name = $printer.Name
            type = "label"
            brand = $brand
            description = "Label Printer"
        }
    }
    return $result
}

# Function to print ZPL
function Print-ZPL {
    param($printerName, $zplData)
    
    try {
        # Create temporary file
        $tempFile = [System.IO.Path]::GetTempFileName() + ".zpl"
        [System.IO.File]::WriteAllText($tempFile, $zplData, [System.Text.Encoding]::UTF8)
        
        # Print using Windows print command
        Start-Process -FilePath "cmd.exe" -ArgumentList "/c copy /b `"$tempFile`" `"\\localhost\$printerName`"" -WindowStyle Hidden -Wait
        
        # Cleanup
        Remove-Item $tempFile -ErrorAction SilentlyContinue
        
        return @{ success = $true; message = "Printed successfully" }
    }
    catch {
        return @{ success = $false; error = $_.Exception.Message }
    }
}

# Main loop
while ($listener.IsListening) {
    $context = $listener.GetContext()
    $request = $context.Request
    $response = $context.Response
    
    $response.ContentType = "application/json"
    $response.Headers.Add("Access-Control-Allow-Origin", "*")
    $response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
    $response.Headers.Add("Access-Control-Allow-Headers", "Content-Type")
    
    # Handle CORS preflight
    if ($request.HttpMethod -eq "OPTIONS") {
        $response.StatusCode = 200
        $response.Close()
        continue
    }
    
    try {
        if ($request.Url.AbsolutePath -eq "/printers" -and $request.HttpMethod -eq "GET") {
            # Get printers
            $printers = Get-LabelPrinters
            $json = @{ printers = $printers } | ConvertTo-Json
            $buffer = [System.Text.Encoding]::UTF8.GetBytes($json)
            $response.ContentLength64 = $buffer.Length
            $response.StatusCode = 200
            $response.OutputStream.Write($buffer, 0, $buffer.Length)
        }
        elseif ($request.Url.AbsolutePath -eq "/print" -and $request.HttpMethod -eq "POST") {
            # Print ZPL
            $reader = New-Object System.IO.StreamReader($request.InputStream)
            $body = $reader.ReadToEnd()
            $data = $body | ConvertFrom-Json
            
            $result = Print-ZPL -printerName $data.printer -zplData $data.zpl
            $json = $result | ConvertTo-Json
            $buffer = [System.Text.Encoding]::UTF8.GetBytes($json)
            $response.ContentLength64 = $buffer.Length
            $response.StatusCode = 200
            $response.OutputStream.Write($buffer, 0, $buffer.Length)
        }
        else {
            $response.StatusCode = 404
            $json = @{ error = "Not found" } | ConvertTo-Json
            $buffer = [System.Text.Encoding]::UTF8.GetBytes($json)
            $response.ContentLength64 = $buffer.Length
            $response.OutputStream.Write($buffer, 0, $buffer.Length)
        }
    }
    catch {
        $response.StatusCode = 500
        $json = @{ error = $_.Exception.Message } | ConvertTo-Json
        $buffer = [System.Text.Encoding]::UTF8.GetBytes($json)
        $response.ContentLength64 = $buffer.Length
        $response.OutputStream.Write($buffer, 0, $buffer.Length)
    }
    
    $response.Close()
}

$listener.Stop()
```

**To run:**
1. Save the script as `local-print-service.ps1`
2. Open PowerShell as Administrator
3. Run: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
4. Run: `.\local-print-service.ps1`

### Option 2: Node.js Service (More Robust)

Create `local-print-service.js`:

```javascript
const http = require('http');
const url = require('url');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

const PORT = 9101;

// Get label printers using Windows PowerShell
async function getLabelPrinters() {
  try {
    const { stdout } = await execPromise(
      `powershell -Command "Get-Printer | Where-Object { $_.Name -like '*Zebra*' -or $_.Name -like '*Honeywell*' -or $_.Name -like '*TSC*' -or $_.Name -like '*TSPL*' } | Select-Object Name | ConvertTo-Json"`
    );
    
    const printers = JSON.parse(stdout);
    const printerList = Array.isArray(printers) ? printers : [printers];
    
    return printerList.map((p, index) => ({
      id: `printer-${index}`,
      name: p.Name,
      type: 'label',
      brand: detectBrand(p.Name),
      description: 'Label Printer',
    }));
  } catch (error) {
    console.error('Error getting printers:', error);
    return [];
  }
}

function detectBrand(name) {
  const lower = name.toLowerCase();
  if (lower.includes('zebra')) return 'Zebra';
  if (lower.includes('honeywell')) return 'Honeywell';
  if (lower.includes('tsc') || lower.includes('tspl')) return 'TSC';
  return 'Unknown';
}

// Print ZPL to printer
async function printZPL(printerName, zplData) {
  const fs = require('fs');
  const path = require('path');
  const os = require('os');
  
  const tempFile = path.join(os.tmpdir(), `label-${Date.now()}.zpl`);
  
  try {
    fs.writeFileSync(tempFile, zplData, 'utf8');
    
    // Use Windows copy command to print raw ZPL
    await execPromise(`cmd /c copy /b "${tempFile}" "\\\\localhost\\${printerName}"`);
    
    fs.unlinkSync(tempFile);
    return { success: true, message: 'Printed successfully' };
  } catch (error) {
    if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile);
    return { success: false, error: error.message };
  }
}

const server = http.createServer(async (req, res) => {
  const parsedUrl = url.parse(req.url, true);
  
  // CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  
  if (req.method === 'OPTIONS') {
    res.writeHead(200);
    res.end();
    return;
  }
  
  // Get printers endpoint
  if (parsedUrl.pathname === '/printers' && req.method === 'GET') {
    try {
      const printers = await getLabelPrinters();
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ printers }));
    } catch (error) {
      res.writeHead(500, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: error.message }));
    }
  }
  // Print endpoint
  else if (parsedUrl.pathname === '/print' && req.method === 'POST') {
    let body = '';
    req.on('data', chunk => { body += chunk.toString(); });
    req.on('end', async () => {
      try {
        const data = JSON.parse(body);
        const result = await printZPL(data.printer, data.zpl);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(result));
      } catch (error) {
        res.writeHead(500, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: error.message }));
      }
    });
  }
  else {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

server.listen(PORT, () => {
  console.log(`Local Print Service running on http://localhost:${PORT}`);
  console.log('Scanning for label printers...');
});

// Handle graceful shutdown
process.on('SIGINT', () => {
  console.log('\nShutting down...');
  server.close(() => {
    process.exit(0);
  });
});
```

**To run:**
1. Install Node.js on Windows machine
2. Save as `local-print-service.js`
3. Run: `node local-print-service.js`

## Configuration in Web App

The web app will automatically try to connect to `http://localhost:9101`. You can change this in the browser console:

```javascript
localStorage.setItem('xyz_pos_print_service_url', 'http://localhost:9101');
```

## Features

- ✅ Auto-detects Zebra, Honeywell, TSC label printers
- ✅ Filters out non-label printers
- ✅ Provides REST API for browser to query
- ✅ Handles ZPL printing directly
- ✅ Works with Windows Print Spooler

## Troubleshooting

1. **Service not detected**: Make sure the service is running on port 9101
2. **No printers found**: Verify printers are installed in Windows Printers & Scanners
3. **Print fails**: Check printer permissions and driver installation
