377 lines
15 KiB
HTML
377 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Mode3 - QuickBooks Integration</title>
|
|
<link rel="stylesheet" href="output.css">
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
border-radius: 10px;
|
|
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
|
padding: 40px;
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
margin: 0 0 10px 0;
|
|
}
|
|
.subtitle {
|
|
color: #666;
|
|
margin-bottom: 30px;
|
|
}
|
|
.card {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
background: #f9f9f9;
|
|
}
|
|
.status-label {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
margin: 5px 0;
|
|
}
|
|
.status-ok {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
.status-error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
button {
|
|
background: #667eea;
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
margin-top: 15px;
|
|
}
|
|
button:hover {
|
|
background: #5568d3;
|
|
}
|
|
.form-group {
|
|
margin: 15px 0;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
input[type="text"], input[type="password"] {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
box-sizing: border-box;
|
|
}
|
|
.message {
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
margin-top: 15px;
|
|
display: none;
|
|
}
|
|
.message.success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
display: block;
|
|
}
|
|
.message.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
display: block;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔌 Mode3 - QuickBooks Integration</h1>
|
|
<p class="subtitle">SOAP Server for QuickBooks Web Connector</p>
|
|
|
|
<div class="card">
|
|
<h2>Server Status</h2>
|
|
<p>Status: <span id="serverStatus" class="status-label status-ok">✓ Running</span></p>
|
|
<p>Mode: <strong>Mode3</strong></p>
|
|
<p>Endpoint: <strong>http://localhost:3005/ws</strong></p>
|
|
<button onclick="checkStatus()">Refresh Status</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>QuickBooks Authentication</h2>
|
|
<div class="form-group">
|
|
<label for="username">Username:</label>
|
|
<input type="text" id="username" placeholder="Enter username" value="admin">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="password">Password:</label>
|
|
<input type="password" id="password" placeholder="Enter password" value="password">
|
|
</div>
|
|
<button onclick="authenticate()">Authenticate with QB</button>
|
|
<div id="authMessage" class="message"></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Session Info</h2>
|
|
<p>Current Session: <span id="sessionStatus" class="status-label status-error">Not authenticated</span></p>
|
|
<div id="sessionDetails" style="display: none;">
|
|
<p><strong>Company File:</strong> <span id="companyFile"></span></p>
|
|
<p><strong>User:</strong> <span id="sessionUser"></span></p>
|
|
<p><strong>Expires:</strong> <span id="sessionExpires"></span></p>
|
|
<button onclick="logout()">Logout</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Enter Credit Card Charge</h2>
|
|
<div class="form-group">
|
|
<label for="ccAccount">Credit Card Account:</label>
|
|
<input type="text" id="ccAccount" placeholder="e.g., 21010 - Kum&Go Fleet Card" value="21010 - Kum&Go Fleet Card">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="vendor">Purchased From (Vendor):</label>
|
|
<input type="text" id="vendor" placeholder="e.g., Gas Station">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="chargeDate">Date:</label>
|
|
<input type="text" id="chargeDate" placeholder="MM/DD/YYYY">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="refNum">Ref No:</label>
|
|
<input type="text" id="refNum" placeholder="e.g., 123456789">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="amount">Amount ($):</label>
|
|
<input type="text" id="amount" placeholder="e.g., 12.00">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="expenseAccount">Expense Account:</label>
|
|
<input type="text" id="expenseAccount" placeholder="e.g., 60000 - Semi-Variable Expenses:50160 - Gas/Fuel">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="memo">Memo:</label>
|
|
<input type="text" id="memo" placeholder="Optional memo">
|
|
</div>
|
|
<button onclick="addCharge()">Add Charge</button>
|
|
<div id="chargeMessage" class="message"></div>
|
|
<button onclick="viewCharges()" style="background: #764ba2;">View Queued Charges</button>
|
|
<div id="chargesList" style="display: none; margin-top: 20px;">
|
|
<h3>Pending Charges</h3>
|
|
<div id="chargesContainer"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Documentation</h2>
|
|
<ul>
|
|
<li><strong>SOAP Endpoint:</strong> <code>POST /ws</code> - QuickBooks Web Connector SOAP interface</li>
|
|
<li><strong>WSDL:</strong> <code>GET /ws</code> - WSDL definition for QB Web Connector</li>
|
|
<li><strong>Status:</strong> <code>GET /quickbooks/status</code> - Check authentication status</li>
|
|
<li><strong>Auth:</strong> <code>POST /quickbooks/auth</code> - REST authentication endpoint</li>
|
|
<li><strong>Health:</strong> <code>GET /health</code> - Server health check</li>
|
|
<li><strong>Charges:</strong> <code>POST /api/charges</code> - Submit credit card charge</li>
|
|
<li><strong>Charges List:</strong> <code>GET /api/charges</code> - List all charges</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Set today's date as default
|
|
function setDefaultDate() {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
document.getElementById('chargeDate').value = today;
|
|
}
|
|
|
|
async function addCharge() {
|
|
const msgDiv = document.getElementById('chargeMessage');
|
|
try {
|
|
const charge = {
|
|
creditCardAccount: document.getElementById('ccAccount').value || '21010 - Kum&Go Fleet Card',
|
|
vendor: document.getElementById('vendor').value || 'Unknown Vendor',
|
|
amount: document.getElementById('amount').value,
|
|
expenseAccount: document.getElementById('expenseAccount').value || '60000 - Semi-Variable Expenses',
|
|
date: document.getElementById('chargeDate').value,
|
|
refNum: document.getElementById('refNum').value || undefined,
|
|
memo: document.getElementById('memo').value || undefined
|
|
};
|
|
|
|
if (!charge.vendor || !charge.amount) {
|
|
msgDiv.className = 'message error';
|
|
msgDiv.textContent = '✗ Vendor and Amount are required';
|
|
return;
|
|
}
|
|
|
|
const res = await fetch('/api/charges', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(charge)
|
|
});
|
|
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
msgDiv.className = 'message success';
|
|
msgDiv.textContent = '✓ ' + data.message + ' (ID: ' + data.charge.id + ')';
|
|
// Clear form
|
|
document.getElementById('vendor').value = '';
|
|
document.getElementById('amount').value = '';
|
|
document.getElementById('refNum').value = '';
|
|
document.getElementById('memo').value = '';
|
|
setDefaultDate();
|
|
viewCharges();
|
|
} else {
|
|
msgDiv.className = 'message error';
|
|
msgDiv.textContent = '✗ ' + data.error;
|
|
}
|
|
} catch (error) {
|
|
msgDiv.className = 'message error';
|
|
msgDiv.textContent = '✗ Failed to add charge: ' + error.message;
|
|
}
|
|
}
|
|
|
|
async function viewCharges() {
|
|
const container = document.getElementById('chargesContainer');
|
|
try {
|
|
const res = await fetch('/api/charges');
|
|
const data = await res.json();
|
|
|
|
let html = `<p><strong>Total Charges: ${data.total}</strong></p>`;
|
|
|
|
if (data.pending.length > 0) {
|
|
html += '<h4>Pending (Not Yet Sent)</h4><ul>';
|
|
data.pending.forEach(ch => {
|
|
html += `<li>${ch.vendor} - $${ch.amount} on ${ch.date} (${ch.id})</li>`;
|
|
});
|
|
html += '</ul>';
|
|
}
|
|
|
|
if (data.sent.length > 0) {
|
|
html += '<h4>Sent to QB (Awaiting Processing)</h4><ul>';
|
|
data.sent.forEach(ch => {
|
|
html += `<li>${ch.vendor} - $${ch.amount} on ${ch.date} (${ch.id})</li>`;
|
|
});
|
|
html += '</ul>';
|
|
}
|
|
|
|
if (data.total === 0) {
|
|
html += '<p><em>No charges queued</em></p>';
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
document.getElementById('chargesList').style.display = 'block';
|
|
} catch (error) {
|
|
container.innerHTML = '<p class="error">Failed to load charges</p>';
|
|
}
|
|
}
|
|
|
|
|
|
async function checkStatus() {
|
|
try {
|
|
const res = await fetch('/health');
|
|
const data = await res.json();
|
|
document.getElementById('serverStatus').className = 'status-label status-ok';
|
|
document.getElementById('serverStatus').textContent = '✓ Running';
|
|
updateSessionInfo();
|
|
} catch (error) {
|
|
document.getElementById('serverStatus').className = 'status-label status-error';
|
|
document.getElementById('serverStatus').textContent = '✗ Offline';
|
|
}
|
|
}
|
|
|
|
// ... rest of existing code remains the same ...
|
|
|
|
// Check status on page load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
setDefaultDate();
|
|
checkStatus();
|
|
updateSessionInfo();
|
|
});
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
const messageDiv = document.getElementById('authMessage');
|
|
|
|
try {
|
|
const res = await fetch('/quickbooks/auth', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (data.success) {
|
|
messageDiv.className = 'message success';
|
|
messageDiv.textContent = '✓ ' + data.message;
|
|
setTimeout(() => updateSessionInfo(), 500);
|
|
} else {
|
|
messageDiv.className = 'message error';
|
|
messageDiv.textContent = '✗ ' + data.error;
|
|
}
|
|
} catch (error) {
|
|
messageDiv.className = 'message error';
|
|
messageDiv.textContent = '✗ Connection failed';
|
|
}
|
|
}
|
|
|
|
async function updateSessionInfo() {
|
|
try {
|
|
const res = await fetch('/quickbooks/status');
|
|
const data = await res.json();
|
|
|
|
if (data.authenticated) {
|
|
document.getElementById('sessionStatus').className = 'status-label status-ok';
|
|
document.getElementById('sessionStatus').textContent = '✓ Authenticated';
|
|
document.getElementById('companyFile').textContent = data.companyFile;
|
|
document.getElementById('sessionUser').textContent = data.userName;
|
|
document.getElementById('sessionExpires').textContent = data.sessionExpires;
|
|
document.getElementById('sessionDetails').style.display = 'block';
|
|
} else {
|
|
document.getElementById('sessionStatus').className = 'status-label status-error';
|
|
document.getElementById('sessionStatus').textContent = '✗ Not authenticated';
|
|
document.getElementById('sessionDetails').style.display = 'none';
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to update session info:', error);
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
try {
|
|
const res = await fetch('/quickbooks/logout', { method: 'POST' });
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
updateSessionInfo();
|
|
document.getElementById('authMessage').className = 'message success';
|
|
document.getElementById('authMessage').textContent = '✓ ' + data.message;
|
|
}
|
|
} catch (error) {
|
|
console.error('Logout failed:', error);
|
|
}
|
|
}
|
|
|
|
// Check status on page load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
checkStatus();
|
|
updateSessionInfo();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|