datecs_sk1_21 0.1.0 copy "datecs_sk1_21: ^0.1.0" to clipboard
datecs_sk1_21: ^0.1.0 copied to clipboard

Complete Dart SDK for Datecs SK1-21 fiscal thermal printer. Supports fiscal receipts, reports, PLU items, VAT rates, electronic journal, fiscal memory, and display control via DUDE COM driver on Windows.

example/example.dart

/// Interactive CLI example for Datecs SK1-21 fiscal printer.
///
/// Run with: dart run example/example.dart
import 'dart:io';
import 'package:datecs_sk1_21/datecs_sk1_21.dart';

late DatecsDudeDriver driver;
bool isConnected = false;

void main() async {
  print('');
  print('╔════════════════════════════════════════════════════════════╗');
  print('║         DATECS SK1-21 - Interactive CLI Example            ║');
  print('╚════════════════════════════════════════════════════════════╝');
  print('');

  driver = DatecsDudeDriver();

  // Listen to events for debugging (optional)
  driver.onError.listen((err) => print('  [ERROR] $err'));
  driver.onCommandSent.listen((cmd) => print('  [CMD] $cmd'));
  driver.onAnswerReceived.listen((ans) => print('  [RSP] $ans'));

  while (true) {
    printMainMenu();
    final choice = readLine('Select option');

    switch (choice) {
      case '1':
        await connectPrinter();
        break;
      case '2':
        await disconnectPrinter();
        break;
      case '3':
        await menuFiscalReceipt();
        break;
      case '4':
        await menuNonFiscalReceipt();
        break;
      case '5':
        await menuReports();
        break;
      case '6':
        await menuDisplay();
        break;
      case '7':
        await menuPaperOperations();
        break;
      case '8':
        await menuCashDrawer();
        break;
      case '9':
        await menuInformation();
        break;
      case '10':
        await menuDateTime();
        break;
      case '11':
        await menuVatRates();
        break;
      case '0':
        await cleanup();
        print('\nGoodbye!\n');
        exit(0);
      default:
        print('  Invalid option. Try again.\n');
    }
  }
}

void printMainMenu() {
  final status = isConnected ? '✓ Connected' : '✗ Not connected';
  print('');
  print('┌─────────────────────────────────────┐');
  print('│            MAIN MENU                │');
  print('│  Status: ${status.padRight(25)} │');
  print('├─────────────────────────────────────┤');
  print('│  1. Connect to printer              │');
  print('│  2. Disconnect                      │');
  print('├─────────────────────────────────────┤');
  print('│  3. Fiscal Receipt                  │');
  print('│  4. Non-Fiscal Receipt              │');
  print('│  5. Reports (X/Z)                   │');
  print('│  6. Display Control                 │');
  print('│  7. Paper Operations                │');
  print('│  8. Cash Drawer                     │');
  print('│  9. Information / Status            │');
  print('│ 10. Date / Time                     │');
  print('│ 11. VAT Rates                       │');
  print('├─────────────────────────────────────┤');
  print('│  0. Exit                            │');
  print('└─────────────────────────────────────┘');
}

// ============ CONNECTION ============

Future<void> connectPrinter() async {
  if (isConnected) {
    print('\n  Already connected.\n');
    return;
  }

  print('\n── Connect to Printer ──\n');
  print('  Connection type:');
  print('    1. RS232 (Serial/USB)');
  print('    2. TCP/IP (Network)');

  final connType = readLine('Select (1 or 2)', defaultValue: '1');

  try {
    await driver.startComServer();

    if (connType == '2') {
      // TCP/IP
      print('\n  TCP/IP Parameters:');
      final address = readLine('  IP Address', defaultValue: '192.168.1.100');
      final port = int.tryParse(
              readLine('  Port (default: 3999)', defaultValue: '3999')) ??
          3999;

      print('\n  Connecting to $address:$port...');
      await driver
          .openTcpIpConnection(TcpIpConfig(address: address, port: port));
    } else {
      // RS232
      print('\n  RS232 Parameters:');
      print('    COM Port: The serial port number (e.g., 4 for COM4)');
      print('    Baud Rate: Communication speed (SK1-21 default: 115200)');

      final comPort =
          int.tryParse(readLine('  COM Port', defaultValue: '4')) ?? 4;
      final baudRate =
          int.tryParse(readLine('  Baud Rate', defaultValue: '115200')) ??
              115200;

      print('\n  Connecting to COM$comPort at $baudRate baud...');
      await driver.openRs232Connection(
          Rs232Config(comPort: comPort, baudRate: baudRate));
    }

    isConnected = true;
    print('  ✓ Connected successfully!');

    if (driver.deviceInfo != null) {
      print('    Model: ${driver.deviceInfo!.model}');
      print('    Serial: ${driver.deviceInfo!.serialNumber}');
      print('    Firmware: ${driver.deviceInfo!.firmwareVersion}');
    }
  } catch (e) {
    print('  ✗ Connection failed: $e');
    isConnected = false;
  }
}

Future<void> disconnectPrinter() async {
  if (!isConnected) {
    print('\n  Not connected.\n');
    return;
  }

  print('\n  Disconnecting...');
  await driver.closeConnection();
  await driver.stopComServer();
  isConnected = false;
  print('  ✓ Disconnected.\n');
}

// ============ FISCAL RECEIPT ============

Future<void> menuFiscalReceipt() async {
  if (!checkConnection()) return;

  print('\n── Fiscal Receipt ──\n');
  print('  1. Print complete receipt (guided)');
  print('  2. Open receipt');
  print('  3. Add sale item');
  print('  4. Print subtotal');
  print('  5. Register payment');
  print('  6. Close receipt');
  print('  7. Cancel receipt');
  print('  8. Print fiscal text');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      await printGuidedFiscalReceipt();
      break;
    case '2':
      await openFiscalReceipt();
      break;
    case '3':
      await addSaleItem();
      break;
    case '4':
      await printSubtotal();
      break;
    case '5':
      await registerPayment();
      break;
    case '6':
      await closeFiscalReceipt();
      break;
    case '7':
      await cancelFiscalReceipt();
      break;
    case '8':
      await printFiscalText();
      break;
  }
}

Future<void> printGuidedFiscalReceipt() async {
  print('\n── Guided Fiscal Receipt ──\n');

  // Open receipt
  print('  Opening fiscal receipt...');
  var response = await driver.openFiscalReceipt();
  if (response.hasError) {
    print('  ✗ Error code: ${response.errorCode}');
    print('  ✗ Error type: ${response.error.name}');
    print('  ✗ Error: ${response.errorDescription}');
    if (response.deviceAnswer.isNotEmpty) {
      print('  ✗ Device response: ${response.deviceAnswer}');
    }
    return;
  }
  print('  ✓ Receipt opened\n');

  // Add items
  print('  Add items (enter empty name to finish):');
  while (true) {
    print('');
    final name = readLine('  Product name (empty to finish)');
    if (name.isEmpty) break;

    final price =
        double.tryParse(readLine('  Price', defaultValue: '10.00')) ?? 10.0;
    final qty =
        double.tryParse(readLine('  Quantity', defaultValue: '1')) ?? 1.0;

    print('  Tax groups: A=0%, B=19%, C=9%, D=5%');
    final taxStr =
        readLine('  Tax group (A/B/C/D)', defaultValue: 'B').toUpperCase();
    final taxGroup = _parseTaxGroup(taxStr);

    final item =
        SaleItem(name: name, price: price, quantity: qty, taxGroup: taxGroup);
    response = await driver.registerSale(item);

    if (response.hasError) {
      print('  ✗ Error: ${response.errorDescription}');
    } else {
      print('  ✓ Added: $name x $qty @ $price (Tax ${taxStr})');
    }
  }

  // Payment
  print('\n  Payment types: 0=Cash, 1=Card, 2=Credit');
  final payType =
      int.tryParse(readLine('  Payment type', defaultValue: '0')) ?? 0;
  final paymentType =
      PaymentType.values[payType.clamp(0, PaymentType.values.length - 1)];

  response = await driver.registerPayment(Payment(type: paymentType));
  if (response.hasError) {
    print('  ✗ Payment error: ${response.errorDescription}');
    await driver.cancelFiscalReceipt();
    return;
  }
  print('  ✓ Payment registered: ${paymentType.name}');

  // Close
  response = await driver.closeFiscalReceipt();
  if (response.hasError) {
    print('  ✗ Close error: ${response.errorDescription}');
    return;
  }
  print('  ✓ Receipt closed');

  // Eject
  await driver.ejectPaper();
  print('  ✓ Paper ejected\n');
}

Future<void> openFiscalReceipt() async {
  print('\n── Open Fiscal Receipt ──\n');
  print('  Receipt types: 1=Fiscal, 2=Invoice, 3=Airport');
  final typeCode =
      int.tryParse(readLine('  Receipt type', defaultValue: '1')) ?? 1;
  final type = ReceiptType.values
      .firstWhere((t) => t.code == typeCode, orElse: () => ReceiptType.fiscal);

  print('  Operator code: Identifies the cashier (1-30)');
  final opCode = readLine('  Operator code', defaultValue: '1');

  print('  Operator password: 4-digit password');
  final opPassword = readLine('  Operator password', defaultValue: '0001');

  final response = await driver.openFiscalReceipt(
    type: type,
    operator: OperatorInfo(code: opCode, password: opPassword),
  );

  printResponse('Open Fiscal Receipt', response);
}

Future<void> addSaleItem() async {
  print('\n── Add Sale Item ──\n');

  print('  Product name: Text to print on receipt (max 36 chars)');
  final name = readLine('  Name', defaultValue: 'Product');

  print('  Price: Unit price with up to 2 decimals');
  final price =
      double.tryParse(readLine('  Price', defaultValue: '10.00')) ?? 10.0;

  print('  Quantity: Amount sold (decimals allowed for weight items)');
  final qty = double.tryParse(readLine('  Quantity', defaultValue: '1')) ?? 1.0;

  print('  Tax group: A=0%, B=19%, C=9%, D=5%, E-H=custom');
  final taxStr = readLine('  Tax group (A-H)', defaultValue: 'B').toUpperCase();
  final taxGroup = _parseTaxGroup(taxStr);

  print(
      '  Discount type: 0=None, 1=Surcharge%, 2=Discount%, 3=Surcharge value, 4=Discount value');
  final discType =
      int.tryParse(readLine('  Discount type', defaultValue: '0')) ?? 0;
  final discountType =
      DiscountType.values[discType.clamp(0, DiscountType.values.length - 1)];

  double discountValue = 0;
  if (discType > 0) {
    print('  Discount value: Percentage or absolute value');
    discountValue =
        double.tryParse(readLine('  Discount value', defaultValue: '0')) ?? 0;
  }

  final item = SaleItem(
    name: name,
    price: price,
    quantity: qty,
    taxGroup: taxGroup,
    discountType: discountType,
    discountValue: discountValue,
  );

  final response = await driver.registerSale(item);
  printResponse('Register Sale', response);
  print('  Total for item: ${item.total.toStringAsFixed(2)}');
}

Future<void> printSubtotal() async {
  print('\n── Subtotal ──\n');

  print('  Print subtotal: Show subtotal line on receipt');
  final printSub =
      readLine('  Print subtotal? (y/n)', defaultValue: 'y').toLowerCase() ==
          'y';

  print('  Display subtotal: Show on customer display');
  final displaySub =
      readLine('  Display subtotal? (y/n)', defaultValue: 'y').toLowerCase() ==
          'y';

  final response =
      await driver.subtotal(printText: printSub, displayText: displaySub);
  printResponse('Subtotal', response);
}

Future<void> registerPayment() async {
  print('\n── Register Payment ──\n');

  print('  Payment types:');
  print('    0 = Cash');
  print('    1 = Card');
  print('    2 = Credit');
  print('    3 = Meal tickets');
  print('    4 = Value tickets');
  print('    5 = Voucher');
  print('    6 = Modern payment (Apple/Google Pay)');
  print('    7 = Card with cash advance');
  print('    8 = Other');
  print('    9 = Currency');

  final payType =
      int.tryParse(readLine('  Payment type', defaultValue: '0')) ?? 0;
  final paymentType =
      PaymentType.values[payType.clamp(0, PaymentType.values.length - 1)];

  print('  Amount: Payment amount (0 or empty = exact amount due)');
  final amountStr = readLine('  Amount', defaultValue: '0');
  final amount = double.tryParse(amountStr);

  final payment =
      Payment(type: paymentType, amount: amount == 0 ? null : amount);
  final response = await driver.registerPayment(payment);
  printResponse('Register Payment', response);
}

Future<void> closeFiscalReceipt() async {
  print('\n  Closing fiscal receipt...');
  final response = await driver.closeFiscalReceipt();
  printResponse('Close Fiscal Receipt', response);
}

Future<void> cancelFiscalReceipt() async {
  print('\n  Canceling fiscal receipt...');
  final response = await driver.cancelFiscalReceipt();
  printResponse('Cancel Fiscal Receipt', response);
}

Future<void> printFiscalText() async {
  print('\n── Print Fiscal Text ──\n');
  print('  Text to print within fiscal receipt (max 36 chars)');
  final text = readLine('  Text');

  final response = await driver.printFiscalText(text);
  printResponse('Print Fiscal Text', response);
}

// ============ NON-FISCAL RECEIPT ============

Future<void> menuNonFiscalReceipt() async {
  if (!checkConnection()) return;

  print('\n── Non-Fiscal Receipt ──\n');
  print('  1. Print complete receipt (guided)');
  print('  2. Open receipt');
  print('  3. Print text line');
  print('  4. Close receipt');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      await printGuidedNonFiscalReceipt();
      break;
    case '2':
      await openNonFiscalReceipt();
      break;
    case '3':
      await printNonFiscalText();
      break;
    case '4':
      await closeNonFiscalReceipt();
      break;
  }
}

Future<void> printGuidedNonFiscalReceipt() async {
  print('\n── Guided Non-Fiscal Receipt ──\n');

  var response = await driver.openNonFiscalReceipt();
  if (response.hasError) {
    print('  ✗ Error: ${response.errorDescription}');
    return;
  }
  print('  ✓ Receipt opened\n');

  print('  Enter text lines (empty line to finish):');
  while (true) {
    final text = readLine('  ');
    if (text.isEmpty) break;

    response = await driver.printNonFiscalText(text);
    if (response.hasError) {
      print('  ✗ Error: ${response.errorDescription}');
    }
  }

  response = await driver.closeNonFiscalReceipt();
  print('  ✓ Receipt closed');

  await driver.ejectPaper();
  print('  ✓ Paper ejected\n');
}

Future<void> openNonFiscalReceipt() async {
  print('\n  Opening non-fiscal receipt...');
  final response = await driver.openNonFiscalReceipt();
  printResponse('Open Non-Fiscal Receipt', response);
}

Future<void> printNonFiscalText() async {
  print('\n── Print Non-Fiscal Text ──\n');
  print('  Text to print (max 42 chars per line)');
  final text = readLine('  Text');

  final response = await driver.printNonFiscalText(text);
  printResponse('Print Non-Fiscal Text', response);
}

Future<void> closeNonFiscalReceipt() async {
  print('\n  Closing non-fiscal receipt...');
  final response = await driver.closeNonFiscalReceipt();
  printResponse('Close Non-Fiscal Receipt', response);
}

// ============ REPORTS ============

Future<void> menuReports() async {
  if (!checkConnection()) return;

  print('\n── Reports ──\n');
  print('  1. X Report (preview, no reset)');
  print('  2. Z Report (daily closure, resets counters)');
  print('  3. Print diagnostic');
  print('  4. Operators report');
  print('  5. Departments report');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Generating X Report...');
      print('  (This is a preview report - counters are NOT reset)');
      final response =
          await driver.generateDailyReport(DailyReportType.xReport);
      printResponse('X Report', response);
      await driver.ejectPaper();
      break;
    case '2':
      print('\n  ⚠️  WARNING: Z Report will reset daily counters!');
      print('  This can only be done ONCE per day.');
      final confirm = readLine('  Are you sure? (yes/no)', defaultValue: 'no');
      if (confirm.toLowerCase() == 'yes') {
        print('\n  Generating Z Report...');
        final response =
            await driver.generateDailyReport(DailyReportType.zReport);
        printResponse('Z Report', response);
        await driver.ejectPaper();
      } else {
        print('  Canceled.');
      }
      break;
    case '3':
      print('\n  Printing diagnostic...');
      final response = await driver.printDiagnostic();
      printResponse('Print Diagnostic', response);
      await driver.ejectPaper();
      break;
    case '4':
      print('\n  Printing operators report...');
      final response = await driver.printOperatorsReport();
      printResponse('Operators Report', response);
      await driver.ejectPaper();
      break;
    case '5':
      print('\n  Printing departments report...');
      final response = await driver.printDepartmentsReport();
      printResponse('Departments Report', response);
      await driver.ejectPaper();
      break;
  }
}

// ============ DISPLAY ============

Future<void> menuDisplay() async {
  if (!checkConnection()) return;

  print('\n── Display Control ──\n');
  print('  The SK1-21 has a 2-line customer display.');
  print('');
  print('  1. Clear display');
  print('  2. Set upper line');
  print('  3. Set lower line');
  print('  4. Set both lines');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Clearing display...');
      final response = await driver.clearDisplay();
      printResponse('Clear Display', response);
      break;
    case '2':
      print('\n  Upper line text (max 20 chars):');
      final text = readLine('  Text');
      final response = await driver.displayUpperLine(text);
      printResponse('Display Upper Line', response);
      break;
    case '3':
      print('\n  Lower line text (max 20 chars):');
      final text = readLine('  Text');
      final response = await driver.displayLowerLine(text);
      printResponse('Display Lower Line', response);
      break;
    case '4':
      print('\n  Upper line text (max 20 chars):');
      final upper = readLine('  Upper');
      print('  Lower line text (max 20 chars):');
      final lower = readLine('  Lower');
      await driver.clearDisplay();
      await driver.displayUpperLine(upper);
      final response = await driver.displayLowerLine(lower);
      printResponse('Display Both Lines', response);
      break;
  }
}

// ============ PAPER OPERATIONS ============

Future<void> menuPaperOperations() async {
  if (!checkConnection()) return;

  print('\n── Paper Operations ──\n');
  print('  1. Feed paper');
  print('  2. Cut paper');
  print('  3. Eject paper (feed + cut)');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Lines to feed: Number of lines (1-99)');
      final lines = int.tryParse(readLine('  Lines', defaultValue: '5')) ?? 5;
      final response = await driver.paperFeed(lines.clamp(1, 99));
      printResponse('Paper Feed', response);
      break;
    case '2':
      print('\n  Cutting paper...');
      final response = await driver.paperCut();
      printResponse('Paper Cut', response);
      break;
    case '3':
      print('\n  Feed lines before cut: Number of lines (1-99)');
      print('  Note: SK1-21 does NOT have software paper presenter.');
      print('        Use physical button to eject paper after cut.');
      final lines = int.tryParse(readLine('  Lines', defaultValue: '5')) ?? 5;
      final response = await driver.ejectPaper(lines.clamp(1, 99));
      printResponse('Eject Paper', response);
      break;
  }
}

// ============ CASH DRAWER ============

Future<void> menuCashDrawer() async {
  if (!checkConnection()) return;

  print('\n── Cash Drawer ──\n');
  print('  1. Open cash drawer');
  print('  2. Cash in (add money)');
  print('  3. Cash out (remove money)');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Opening cash drawer...');
      final response = await driver.openCashDrawer();
      printResponse('Open Cash Drawer', response);
      break;
    case '2':
      print('\n  Cash In: Register money added to drawer');
      print('  Amount: Positive value to add');
      final amount =
          double.tryParse(readLine('  Amount', defaultValue: '100')) ?? 100;
      final response = await driver.cashInOut(amount.abs());
      printResponse('Cash In', response);
      break;
    case '3':
      print('\n  Cash Out: Register money removed from drawer');
      print('  Amount: Value to remove (will be negative)');
      final amount =
          double.tryParse(readLine('  Amount', defaultValue: '50')) ?? 50;
      final response = await driver.cashInOut(-amount.abs());
      printResponse('Cash Out', response);
      break;
  }
}

// ============ INFORMATION ============

Future<void> menuInformation() async {
  if (!checkConnection()) return;

  print('\n── Information / Status ──\n');
  print('  1. Printer status');
  print('  2. Diagnostic info');
  print('  3. Receipt status');
  print('  4. Current receipt info');
  print('  5. Daily taxation info');
  print('  6. Remaining Z reports');
  print('  7. Read last error');
  print('  8. Check connection');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Reading printer status...');
      final status = await driver.getStatus();
      print('\n  Printer Status:');
      print('    Fiscal receipt open: ${status.fiscalReceiptOpen}');
      print('    Non-fiscal receipt open: ${status.nonFiscalReceiptOpen}');
      print('    Paper near end: ${status.paperNearEnd}');
      print('    Paper out: ${status.paperOut}');
      print('    Fiscal memory full: ${status.fiscalMemoryFull}');
      print('    Fiscal memory near full: ${status.fiscalMemoryNearFull}');
      print('    Fiscalized: ${status.fiscalized}');
      print('    Has error: ${status.hasError}');
      print('    Is ready: ${status.isReady}');
      break;
    case '2':
      print('\n  Reading diagnostic info...');
      final info = await driver.getDiagnosticInfo();
      print('\n  Diagnostic Info:');
      print('    Model: ${info.model}');
      print('    Serial: ${info.serialNumber}');
      print('    Firmware: ${info.firmwareVersion}');
      print('    Fiscal memory serial: ${info.fiscalMemorySerial}');
      print('    Total RAM: ${info.totalRam}');
      print('    Free RAM: ${info.freeRam}');
      break;
    case '3':
      print('\n  Reading receipt status...');
      final status = await driver.getReceiptStatus();
      print('\n  Receipt Status:');
      print('    Receipt open: ${status.isOpen}');
      print('    Fiscal receipt: ${status.isFiscal}');
      print('    Sales count: ${status.salesCount}');
      print('    Subtotal: ${status.subtotal}');
      print('    Receipt number: ${status.receiptNumber}');
      break;
    case '4':
      print('\n  Reading current receipt info...');
      final info = await driver.getCurrentReceiptInfo();
      print('\n  Current Receipt Info:');
      print('    Receipt open: ${info.isOpen}');
      print('    Sales count: ${info.salesCount}');
      print('    Subtotal net: ${info.subtotalNet}');
      print('    Total VAT: ${info.totalVat}');
      print('    Total gross: ${info.totalGross}');
      print('    Receipt number: ${info.receiptNumber}');
      print('    Unique sale number: ${info.uniqueSaleNumber}');
      break;
    case '5':
      print('\n  Reading daily taxation info...');
      final info = await driver.getDailyTaxationInfo();
      print('\n  Daily Taxation:');
      print('    Grand total: ${info.grandTotal}');
      print('    Grand total VAT: ${info.grandTotalVat}');
      print('    Fiscal receipts count: ${info.fiscalReceiptsCount}');
      print('    Cancelled receipts: ${info.cancelledReceiptsCount}');
      print('    Sales by tax group:');
      for (final entry in info.salesByTaxGroup.entries) {
        print('      ${entry.key.name.toUpperCase()}: ${entry.value}');
      }
      break;
    case '6':
      print('\n  Reading remaining Z reports...');
      final remaining = await driver.getRemainingZReports();
      print('\n  Remaining Z Reports: $remaining');
      print('  (Fiscal memory can store max 2500 Z reports)');
      break;
    case '7':
      print('\n  Reading last error...');
      final error = await driver.readLastError();
      print('\n  Last Error: $error');
      break;
    case '8':
      print('\n  Checking connection...');
      final response = await driver.checkConnection();
      printResponse('Check Connection', response);
      break;
  }
}

// ============ DATE/TIME ============

Future<void> menuDateTime() async {
  if (!checkConnection()) return;

  print('\n── Date / Time ──\n');
  print('  1. Read current date/time');
  print('  2. Set date/time');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Reading date/time...');
      final dt = await driver.readDateTime();
      if (dt != null) {
        print(
            '  Current: ${dt.day.toString().padLeft(2, '0')}-${dt.month.toString().padLeft(2, '0')}-${dt.year} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}');
      } else {
        print('  ✗ Could not read date/time');
      }
      break;
    case '2':
      print('\n  Set Date/Time:');
      print('  ⚠️  Can only be set once between Z reports!');
      print('');
      final now = DateTime.now();
      final day =
          int.tryParse(readLine('  Day (1-31)', defaultValue: '${now.day}')) ??
              now.day;
      final month = int.tryParse(
              readLine('  Month (1-12)', defaultValue: '${now.month}')) ??
          now.month;
      final year =
          int.tryParse(readLine('  Year', defaultValue: '${now.year}')) ??
              now.year;
      final hour = int.tryParse(
              readLine('  Hour (0-23)', defaultValue: '${now.hour}')) ??
          now.hour;
      final minute = int.tryParse(
              readLine('  Minute (0-59)', defaultValue: '${now.minute}')) ??
          now.minute;

      final dt = DateTime(year, month, day, hour, minute);
      final response = await driver.setDateTime(dt);
      printResponse('Set Date/Time', response);
      break;
  }
}

// ============ VAT RATES ============

Future<void> menuVatRates() async {
  if (!checkConnection()) return;

  print('\n── VAT Rates ──\n');
  print('  1. Read current VAT rates');
  print('  2. Program VAT rates');
  print('  0. Back');

  final choice = readLine('Select option');

  switch (choice) {
    case '1':
      print('\n  Reading VAT rates...');
      final rates = await driver.readVatRates();
      print('\n  Current VAT Rates:');
      for (final entry in rates.entries) {
        print('    Group ${entry.key.name.toUpperCase()}: ${entry.value}%');
      }
      break;
    case '2':
      print('\n  Program VAT Rates:');
      print('  ⚠️  Can only be changed after Z report!');
      print('');
      final rateA = double.tryParse(
              readLine('  Rate A (default 0%)', defaultValue: '0')) ??
          0;
      final rateB = double.tryParse(
              readLine('  Rate B (default 19%)', defaultValue: '19')) ??
          19;
      final rateC = double.tryParse(
              readLine('  Rate C (default 9%)', defaultValue: '9')) ??
          9;
      final rateD = double.tryParse(
              readLine('  Rate D (default 5%)', defaultValue: '5')) ??
          5;

      final rates = {
        TaxGroup.a: rateA,
        TaxGroup.b: rateB,
        TaxGroup.c: rateC,
        TaxGroup.d: rateD,
      };

      final response = await driver.programVatRates(rates);
      printResponse('Program VAT Rates', response);
      break;
  }
}

// ============ HELPERS ============

String readLine(String prompt, {String defaultValue = ''}) {
  final defaultHint = defaultValue.isNotEmpty ? ' [$defaultValue]' : '';
  stdout.write('$prompt$defaultHint: ');
  final input = stdin.readLineSync() ?? '';
  return input.isEmpty ? defaultValue : input;
}

bool checkConnection() {
  if (!isConnected) {
    print('\n  ✗ Not connected. Please connect first.\n');
    return false;
  }
  return true;
}

void printResponse(String operation, DatecsResponse response) {
  print('');
  print('  ┌─ $operation ─');
  print('  │ Success: ${!response.hasError}');
  if (response.hasError) {
    print('  │ Error code: ${response.errorCode}');
    print('  │ Error type: ${response.error.name}');
    print('  │ Error: ${response.errorDescription}');
  }
  if (response.deviceAnswer.isNotEmpty) {
    print('  │ Response: ${response.deviceAnswer}');
  }
  print('  └────────────────────');
}

TaxGroup _parseTaxGroup(String s) {
  switch (s.toUpperCase()) {
    case 'A':
      return TaxGroup.a;
    case 'C':
      return TaxGroup.c;
    case 'D':
      return TaxGroup.d;
    case 'E':
      return TaxGroup.e;
    case 'F':
      return TaxGroup.f;
    case 'G':
      return TaxGroup.g;
    case 'H':
      return TaxGroup.h;
    default:
      return TaxGroup.b;
  }
}

Future<void> cleanup() async {
  if (isConnected) {
    await driver.closeConnection();
    await driver.stopComServer();
  }
  driver.dispose();
}
0
likes
140
points
28
downloads

Publisher

verified publishericefelix.com

Weekly Downloads

Complete Dart SDK for Datecs SK1-21 fiscal thermal printer. Supports fiscal receipts, reports, PLU items, VAT rates, electronic journal, fiscal memory, and display control via DUDE COM driver on Windows.

Homepage
Repository (GitHub)
View/report issues

Topics

#printer #fiscal #pos #thermal-printer #datecs

Documentation

Documentation
API reference

License

MIT (license)

More

Packages that depend on datecs_sk1_21