Add source
This commit is contained in:
118
pom.xml
Normal file
118
pom.xml
Normal file
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.excelaccess</groupId>
|
||||
<artifactId>excel-access-migration</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Excel to Access Migration Tool</name>
|
||||
<description>Tool for migrating data from Excel files to Microsoft Access database</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>24</maven.compiler.source>
|
||||
<maven.compiler.target>24</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- UCanAccess for Microsoft Access database connectivity -->
|
||||
<dependency>
|
||||
<groupId>net.sf.ucanaccess</groupId>
|
||||
<artifactId>ucanaccess</artifactId>
|
||||
<version>5.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache POI for Excel file processing -->
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson for JSON configuration processing -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFX for UI -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>21.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>21.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<mainClass>com.excelaccess.MainApplication</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.excelaccess.MainApplication</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
79
src/main/java/com/excelaccess/MainApplication.java
Normal file
79
src/main/java/com/excelaccess/MainApplication.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package com.excelaccess;
|
||||
|
||||
import com.excelaccess.controller.MainController;
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MainApplication extends Application {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainApplication.class);
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
try {
|
||||
logger.info("Starting Excel to Access Migration Tool");
|
||||
|
||||
// Load FXML
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"));
|
||||
Scene scene = new Scene(loader.load());
|
||||
|
||||
// Load CSS
|
||||
scene.getStylesheets().add(getClass().getResource("/css/styles.css").toExternalForm());
|
||||
|
||||
// Set up controller
|
||||
MainController controller = loader.getController();
|
||||
controller.setPrimaryStage(primaryStage);
|
||||
|
||||
// Configure stage
|
||||
primaryStage.setTitle("Инструмент миграции Excel в Access");
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.setResizable(true);
|
||||
primaryStage.setMinWidth(800);
|
||||
primaryStage.setMinHeight(700);
|
||||
|
||||
// Set application icon (optional)
|
||||
try {
|
||||
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/app-icon.png")));
|
||||
} catch (Exception e) {
|
||||
logger.debug("Application icon not found, using default");
|
||||
}
|
||||
|
||||
// Show stage
|
||||
primaryStage.show();
|
||||
|
||||
logger.info("Application started successfully");
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to start application", e);
|
||||
showErrorDialog("Failed to start application", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
logger.info("Application is shutting down");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Set system properties for better logging
|
||||
System.setProperty("logback.configurationFile", "src/main/resources/logback.xml");
|
||||
|
||||
logger.info("Launching Excel to Access Migration Tool");
|
||||
launch(args);
|
||||
}
|
||||
|
||||
private void showErrorDialog(String title, String message) {
|
||||
javafx.scene.control.Alert alert = new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.excelaccess.controller;
|
||||
|
||||
import com.excelaccess.service.ExcelAnalysisService;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class AnalysisDialogController implements Initializable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnalysisDialogController.class);
|
||||
|
||||
@FXML
|
||||
private TableView<ProductCardModel> productCardsTable;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductCardModel, String> nameColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductCardModel, String> supplierColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductCardModel, String> projectColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductCardModel, String> categoryColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<ProductCardModel, String> articleColumn;
|
||||
|
||||
@FXML
|
||||
private TableView<TransactionModel> transactionsTable;
|
||||
|
||||
@FXML
|
||||
private TableColumn<TransactionModel, String> transactionNameColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<TransactionModel, Integer> incomeColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<TransactionModel, String> whoColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<TransactionModel, String> dateColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<TransactionModel, String> invoiceColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<TransactionModel, String> taskColumn;
|
||||
|
||||
@FXML
|
||||
private TextArea statisticsArea;
|
||||
|
||||
private ExcelAnalysisService.AnalysisResult analysisResult;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
setupTableColumns();
|
||||
}
|
||||
|
||||
private void setupTableColumns() {
|
||||
// Настройка колонок для карточек товаров
|
||||
nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
|
||||
supplierColumn.setCellValueFactory(cellData -> cellData.getValue().supplierProperty());
|
||||
projectColumn.setCellValueFactory(cellData -> cellData.getValue().projectProperty());
|
||||
categoryColumn.setCellValueFactory(cellData -> cellData.getValue().categoryProperty());
|
||||
articleColumn.setCellValueFactory(cellData -> cellData.getValue().articleProperty());
|
||||
|
||||
// Настройка колонок для транзакций
|
||||
transactionNameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
|
||||
incomeColumn.setCellValueFactory(cellData -> cellData.getValue().incomeProperty().asObject());
|
||||
whoColumn.setCellValueFactory(cellData -> cellData.getValue().whoProperty());
|
||||
dateColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty());
|
||||
invoiceColumn.setCellValueFactory(cellData -> cellData.getValue().invoiceProperty());
|
||||
taskColumn.setCellValueFactory(cellData -> cellData.getValue().taskProperty());
|
||||
}
|
||||
|
||||
public void setAnalysisResult(ExcelAnalysisService.AnalysisResult result) {
|
||||
this.analysisResult = result;
|
||||
populateTables();
|
||||
updateStatistics();
|
||||
}
|
||||
|
||||
private void populateTables() {
|
||||
if (analysisResult == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Заполнение таблицы карточек товаров
|
||||
ObservableList<ProductCardModel> productCards = FXCollections.observableArrayList();
|
||||
for (ExcelAnalysisService.ProductCard card : analysisResult.getProductCards()) {
|
||||
productCards.add(new ProductCardModel(card));
|
||||
}
|
||||
productCardsTable.setItems(productCards);
|
||||
|
||||
// Заполнение таблицы транзакций
|
||||
ObservableList<TransactionModel> transactions = FXCollections.observableArrayList();
|
||||
for (ExcelAnalysisService.Transaction transaction : analysisResult.getTransactions()) {
|
||||
transactions.add(new TransactionModel(transaction));
|
||||
}
|
||||
transactionsTable.setItems(transactions);
|
||||
}
|
||||
|
||||
private void updateStatistics() {
|
||||
if (analysisResult == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder stats = new StringBuilder();
|
||||
stats.append("Общая статистика:\n");
|
||||
stats.append("• Карточек товаров: ").append(analysisResult.getProductCards().size()).append("\n");
|
||||
stats.append("• Транзакций: ").append(analysisResult.getTransactions().size()).append("\n");
|
||||
|
||||
if (analysisResult.getCrossAnalysis() != null) {
|
||||
stats.append("• Товаров с транзакциями: ").append(analysisResult.getCrossAnalysis().getProductsWithTransactions().size()).append("\n");
|
||||
stats.append("• Товаров без транзакций: ").append(analysisResult.getCrossAnalysis().getProductsWithoutTransactions().size()).append("\n");
|
||||
stats.append("• Неизвестных транзакций: ").append(analysisResult.getCrossAnalysis().getUnknownProductTransactions().size()).append("\n");
|
||||
}
|
||||
|
||||
statisticsArea.setText(stats.toString());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void closeDialog() {
|
||||
Stage stage = (Stage) statisticsArea.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
// Модели для таблиц
|
||||
public static class ProductCardModel {
|
||||
private final javafx.beans.property.SimpleStringProperty name;
|
||||
private final javafx.beans.property.SimpleStringProperty supplier;
|
||||
private final javafx.beans.property.SimpleStringProperty project;
|
||||
private final javafx.beans.property.SimpleStringProperty category;
|
||||
private final javafx.beans.property.SimpleStringProperty article;
|
||||
|
||||
public ProductCardModel(ExcelAnalysisService.ProductCard card) {
|
||||
this.name = new javafx.beans.property.SimpleStringProperty(card.getName());
|
||||
this.supplier = new javafx.beans.property.SimpleStringProperty(card.getSupplierName());
|
||||
this.project = new javafx.beans.property.SimpleStringProperty(card.getProject());
|
||||
this.category = new javafx.beans.property.SimpleStringProperty(card.getCategory());
|
||||
this.article = new javafx.beans.property.SimpleStringProperty(card.getArticleNumbers());
|
||||
}
|
||||
|
||||
public javafx.beans.property.StringProperty nameProperty() { return name; }
|
||||
public javafx.beans.property.StringProperty supplierProperty() { return supplier; }
|
||||
public javafx.beans.property.StringProperty projectProperty() { return project; }
|
||||
public javafx.beans.property.StringProperty categoryProperty() { return category; }
|
||||
public javafx.beans.property.StringProperty articleProperty() { return article; }
|
||||
}
|
||||
|
||||
public static class TransactionModel {
|
||||
private final javafx.beans.property.SimpleStringProperty name;
|
||||
private final javafx.beans.property.SimpleIntegerProperty income;
|
||||
private final javafx.beans.property.SimpleStringProperty who;
|
||||
private final javafx.beans.property.SimpleStringProperty date;
|
||||
private final javafx.beans.property.SimpleStringProperty invoice;
|
||||
private final javafx.beans.property.SimpleStringProperty task;
|
||||
|
||||
public TransactionModel(ExcelAnalysisService.Transaction transaction) {
|
||||
this.name = new javafx.beans.property.SimpleStringProperty(transaction.getName());
|
||||
this.income = new javafx.beans.property.SimpleIntegerProperty(transaction.getIncome() != null ? transaction.getIncome() : 0);
|
||||
this.who = new javafx.beans.property.SimpleStringProperty(transaction.getWho());
|
||||
this.date = new javafx.beans.property.SimpleStringProperty(
|
||||
transaction.getDate() != null ?
|
||||
transaction.getDate().format(DateTimeFormatter.ofPattern("dd.MM.yyyy")) :
|
||||
""
|
||||
);
|
||||
this.invoice = new javafx.beans.property.SimpleStringProperty(transaction.getInvoiceNumber());
|
||||
this.task = new javafx.beans.property.SimpleStringProperty(transaction.getTaskNumber());
|
||||
}
|
||||
|
||||
public javafx.beans.property.StringProperty nameProperty() { return name; }
|
||||
public javafx.beans.property.IntegerProperty incomeProperty() { return income; }
|
||||
public javafx.beans.property.StringProperty whoProperty() { return who; }
|
||||
public javafx.beans.property.StringProperty dateProperty() { return date; }
|
||||
public javafx.beans.property.StringProperty invoiceProperty() { return invoice; }
|
||||
public javafx.beans.property.StringProperty taskProperty() { return task; }
|
||||
}
|
||||
}
|
||||
346
src/main/java/com/excelaccess/controller/MainController.java
Normal file
346
src/main/java/com/excelaccess/controller/MainController.java
Normal file
@@ -0,0 +1,346 @@
|
||||
package com.excelaccess.controller;
|
||||
|
||||
import com.excelaccess.service.MigrationService;
|
||||
import com.excelaccess.service.ConfigService;
|
||||
import com.excelaccess.service.ExcelAnalysisService;
|
||||
import com.excelaccess.service.SqlGeneratorService;
|
||||
import com.excelaccess.model.MappingConfig;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MainController implements Initializable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainController.class);
|
||||
|
||||
@FXML private TextField excelFilePath;
|
||||
@FXML private TextField accessFilePath;
|
||||
@FXML private Button browseButton;
|
||||
@FXML private Button browseAccessButton;
|
||||
@FXML private Button analyzeButton;
|
||||
@FXML private Button startButton;
|
||||
@FXML private Button stopButton;
|
||||
@FXML private Button clearButton;
|
||||
@FXML private ProgressBar progressBar;
|
||||
@FXML private Label statusLabel;
|
||||
@FXML private TextArea logArea;
|
||||
|
||||
private MigrationService migrationService;
|
||||
private ConfigService configService;
|
||||
private ExcelAnalysisService analysisService;
|
||||
private SqlGeneratorService sqlGeneratorService;
|
||||
private Stage primaryStage;
|
||||
private boolean isMigrationRunning = false;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
migrationService = new MigrationService();
|
||||
configService = new ConfigService();
|
||||
analysisService = new ExcelAnalysisService();
|
||||
sqlGeneratorService = new SqlGeneratorService();
|
||||
|
||||
// Load Access file path from configuration
|
||||
loadAccessFilePathFromConfig();
|
||||
|
||||
// Initialize UI state
|
||||
updateUIState();
|
||||
|
||||
// Add log appender to show logs in UI
|
||||
setupLogAppender();
|
||||
}
|
||||
|
||||
private void loadAccessFilePathFromConfig() {
|
||||
try {
|
||||
String savedAccessPath = configService.getAccessFilePathFromConfig();
|
||||
if (savedAccessPath != null && !savedAccessPath.isEmpty()) {
|
||||
accessFilePath.setText(savedAccessPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to load Access file path from configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPrimaryStage(Stage stage) {
|
||||
this.primaryStage = stage;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void browseExcelFile() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Выберите файл Excel");
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Файлы Excel", "*.xlsx", "*.xls"),
|
||||
new FileChooser.ExtensionFilter("Все файлы", "*.*")
|
||||
);
|
||||
|
||||
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
||||
if (selectedFile != null) {
|
||||
excelFilePath.setText(selectedFile.getAbsolutePath());
|
||||
logMessage("Выбран файл Excel: " + selectedFile.getName());
|
||||
updateUIState();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void browseAccessFile() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Выберите базу данных Access");
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("Файлы Access", "*.accdb", "*.mdb"),
|
||||
new FileChooser.ExtensionFilter("Все файлы", "*.*")
|
||||
);
|
||||
|
||||
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
||||
if (selectedFile != null) {
|
||||
accessFilePath.setText(selectedFile.getAbsolutePath());
|
||||
logMessage("Выбрана база данных Access: " + selectedFile.getName());
|
||||
|
||||
// Save Access file path to configuration
|
||||
try {
|
||||
configService.updateAccessFilePath(selectedFile.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to save Access file path to configuration", e);
|
||||
}
|
||||
|
||||
updateUIState();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void analyzeExcel() {
|
||||
String excelFile = excelFilePath.getText();
|
||||
|
||||
if (excelFile.isEmpty()) {
|
||||
showAlert("Ошибка", "Сначала выберите файл Excel.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
statusLabel.setText("Анализ файла Excel...");
|
||||
logMessage("Начинается анализ файла Excel...");
|
||||
|
||||
// Run analysis in background thread
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
ExcelAnalysisService.AnalysisResult result = analysisService.analyzeExcelFile(excelFile);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
openAnalysisDialog(result);
|
||||
statusLabel.setText("Анализ Excel завершен успешно!");
|
||||
logMessage("Анализ Excel завершен успешно!");
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
statusLabel.setText("Ошибка анализа Excel: " + e.getMessage());
|
||||
logMessage("Ошибка анализа: " + e.getMessage());
|
||||
logger.error("Excel analysis failed", e);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
showAlert("Ошибка", "Не удалось начать анализ Excel: " + e.getMessage());
|
||||
logger.error("Failed to start Excel analysis", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void openAnalysisDialog(ExcelAnalysisService.AnalysisResult result) {
|
||||
try {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/analysis-dialog.fxml"));
|
||||
Dialog<ButtonType> dialog = loader.load();
|
||||
AnalysisDialogController controller = loader.getController();
|
||||
controller.setAnalysisResult(result);
|
||||
|
||||
dialog.setTitle("Анализ Excel файла");
|
||||
dialog.setHeaderText("Результаты анализа данных Excel");
|
||||
|
||||
Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
|
||||
stage.setResizable(true);
|
||||
|
||||
dialog.showAndWait();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to open analysis dialog", e);
|
||||
showAlert("Ошибка", "Не удалось открыть диалог анализа: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void displayAnalysisResults(ExcelAnalysisService.AnalysisResult result) {
|
||||
appendLog("=== ГЕНЕРАЦИЯ SQL ЗАПРОСОВ ===");
|
||||
|
||||
if (result.getProductCards() != null && result.getTransactions() != null) {
|
||||
// Generate SQL queries
|
||||
SqlGeneratorService.SqlGenerationResult sqlResult = sqlGeneratorService.generateSqlQueries(
|
||||
result.getProductCards(), result.getTransactions());
|
||||
|
||||
// Display INSERT statements for Склад
|
||||
appendLog("=== ВСТАВКА ДАННЫХ В ТАБЛИЦУ СКЛАД ===");
|
||||
for (String statement : sqlResult.getSkladInsertStatements()) {
|
||||
appendLog(statement);
|
||||
}
|
||||
|
||||
// Display INSERT statements for Транзакции
|
||||
appendLog("=== ВСТАВКА ДАННЫХ В ТАБЛИЦУ ТРАНЗАКЦИИ ===");
|
||||
for (String statement : sqlResult.getTransactionsInsertStatements()) {
|
||||
appendLog(statement);
|
||||
}
|
||||
|
||||
// Display summary
|
||||
appendLog("=== СВОДКА ===");
|
||||
appendLog(String.format("Товаров для вставки в Склад: %d", result.getProductCards().size()));
|
||||
appendLog(String.format("Транзакций для вставки: %d", result.getTransactions().size()));
|
||||
appendLog("");
|
||||
appendLog("ВНИМАНИЕ: После выполнения INSERT запросов нужно обновить связи между таблицами!");
|
||||
appendLog("Используйте UPDATE запросы, которые уже включены в результат.");
|
||||
}
|
||||
|
||||
appendLog("=== ГЕНЕРАЦИЯ SQL ЗАВЕРШЕНА ===");
|
||||
}
|
||||
|
||||
private String truncateString(String str, int maxLength) {
|
||||
if (str == null) return "";
|
||||
if (str.length() <= maxLength) return str;
|
||||
return str.substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
|
||||
private static class ProductSummary {
|
||||
String name = "";
|
||||
String supplierName = "";
|
||||
int count = 0;
|
||||
Set<String> invoiceNumbers = new HashSet<>();
|
||||
String articleNumbers = "";
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void startMigration() {
|
||||
if (isMigrationRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String excelFile = excelFilePath.getText();
|
||||
String accessFile = accessFilePath.getText();
|
||||
|
||||
if (excelFile.isEmpty() || accessFile.isEmpty()) {
|
||||
showAlert("Ошибка", "Выберите файл Excel и базу данных Access.");
|
||||
return;
|
||||
}
|
||||
|
||||
isMigrationRunning = true;
|
||||
updateUIState();
|
||||
statusLabel.setText("Миграция начата...");
|
||||
progressBar.setProgress(0);
|
||||
logMessage("Начинается миграция данных...");
|
||||
|
||||
// Run migration in background thread
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
migrationService.migrateDataWithAnalysis(excelFile, accessFile, this::updateProgress);
|
||||
Platform.runLater(() -> {
|
||||
statusLabel.setText("Миграция завершена успешно!");
|
||||
progressBar.setProgress(1.0);
|
||||
isMigrationRunning = false;
|
||||
logMessage("Миграция завершена успешно!");
|
||||
updateUIState();
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
statusLabel.setText("Ошибка миграции: " + e.getMessage());
|
||||
isMigrationRunning = false;
|
||||
logMessage("Ошибка миграции: " + e.getMessage());
|
||||
updateUIState();
|
||||
logger.error("Migration failed", e);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
showAlert("Ошибка", "Не удалось начать миграцию: " + e.getMessage());
|
||||
logger.error("Failed to start migration", e);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void stopMigration() {
|
||||
if (isMigrationRunning) {
|
||||
migrationService.stopMigration();
|
||||
isMigrationRunning = false;
|
||||
updateUIState();
|
||||
statusLabel.setText("Migration stopped by user.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void clearFields() {
|
||||
excelFilePath.clear();
|
||||
accessFilePath.clear();
|
||||
logArea.clear();
|
||||
progressBar.setProgress(0);
|
||||
statusLabel.setText("Готов к началу миграции");
|
||||
logMessage("Поля очищены");
|
||||
updateUIState();
|
||||
}
|
||||
|
||||
private void updateProgress(double progress) {
|
||||
Platform.runLater(() -> {
|
||||
progressBar.setProgress(progress);
|
||||
statusLabel.setText(String.format("Прогресс миграции: %.1f%%", progress * 100));
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUIState() {
|
||||
boolean hasExcelFile = !excelFilePath.getText().isEmpty();
|
||||
boolean hasAllFiles = hasExcelFile && !accessFilePath.getText().isEmpty();
|
||||
|
||||
analyzeButton.setDisable(isMigrationRunning || !hasExcelFile);
|
||||
startButton.setDisable(isMigrationRunning || !hasAllFiles);
|
||||
browseButton.setDisable(isMigrationRunning);
|
||||
browseAccessButton.setDisable(isMigrationRunning);
|
||||
}
|
||||
|
||||
private void setupLogAppender() {
|
||||
// This would typically be done with a custom log appender
|
||||
// For now, we'll use a simple approach
|
||||
}
|
||||
|
||||
public void appendLog(String message) {
|
||||
Platform.runLater(() -> {
|
||||
logArea.appendText(message + "\n");
|
||||
logArea.setScrollTop(Double.MAX_VALUE);
|
||||
});
|
||||
}
|
||||
|
||||
private void logMessage(String message) {
|
||||
Platform.runLater(() -> {
|
||||
String timestamp = java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss"));
|
||||
logArea.appendText("[" + timestamp + "] " + message + "\n");
|
||||
logArea.setScrollTop(Double.MAX_VALUE);
|
||||
});
|
||||
}
|
||||
|
||||
private void showAlert(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/excelaccess/model/ApiConfig.java
Normal file
36
src/main/java/com/excelaccess/model/ApiConfig.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.excelaccess.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class ApiConfig {
|
||||
@JsonProperty("base_url")
|
||||
private String baseUrl;
|
||||
|
||||
@JsonProperty("api_get_label_id")
|
||||
private String apiGetLabelId;
|
||||
|
||||
// Constructors
|
||||
public ApiConfig() {}
|
||||
|
||||
public ApiConfig(String baseUrl, String apiGetLabelId) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.apiGetLabelId = apiGetLabelId;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public String getApiGetLabelId() {
|
||||
return apiGetLabelId;
|
||||
}
|
||||
|
||||
public void setApiGetLabelId(String apiGetLabelId) {
|
||||
this.apiGetLabelId = apiGetLabelId;
|
||||
}
|
||||
}
|
||||
48
src/main/java/com/excelaccess/model/ColumnMapping.java
Normal file
48
src/main/java/com/excelaccess/model/ColumnMapping.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.excelaccess.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class ColumnMapping {
|
||||
@JsonProperty("excelColumn")
|
||||
private String excelColumn;
|
||||
|
||||
@JsonProperty("accessColumn")
|
||||
private String accessColumn;
|
||||
|
||||
@JsonProperty("dataType")
|
||||
private String dataType;
|
||||
|
||||
// Constructors
|
||||
public ColumnMapping() {}
|
||||
|
||||
public ColumnMapping(String excelColumn, String accessColumn, String dataType) {
|
||||
this.excelColumn = excelColumn;
|
||||
this.accessColumn = accessColumn;
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getExcelColumn() {
|
||||
return excelColumn;
|
||||
}
|
||||
|
||||
public void setExcelColumn(String excelColumn) {
|
||||
this.excelColumn = excelColumn;
|
||||
}
|
||||
|
||||
public String getAccessColumn() {
|
||||
return accessColumn;
|
||||
}
|
||||
|
||||
public void setAccessColumn(String accessColumn) {
|
||||
this.accessColumn = accessColumn;
|
||||
}
|
||||
|
||||
public String getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public void setDataType(String dataType) {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
}
|
||||
24
src/main/java/com/excelaccess/model/DatabaseConfig.java
Normal file
24
src/main/java/com/excelaccess/model/DatabaseConfig.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.excelaccess.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class DatabaseConfig {
|
||||
@JsonProperty("connectionString")
|
||||
private String connectionString;
|
||||
|
||||
// Constructors
|
||||
public DatabaseConfig() {}
|
||||
|
||||
public DatabaseConfig(String connectionString) {
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getConnectionString() {
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
public void setConnectionString(String connectionString) {
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
}
|
||||
61
src/main/java/com/excelaccess/model/MappingConfig.java
Normal file
61
src/main/java/com/excelaccess/model/MappingConfig.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.excelaccess.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
public class MappingConfig {
|
||||
@JsonProperty("database")
|
||||
private DatabaseConfig database;
|
||||
|
||||
@JsonProperty("mappings")
|
||||
private List<SheetMapping> mappings;
|
||||
|
||||
@JsonProperty("settings")
|
||||
private MigrationSettings settings;
|
||||
|
||||
@JsonProperty("dsol_factory")
|
||||
private ApiConfig dsolFactory;
|
||||
|
||||
// Constructors
|
||||
public MappingConfig() {}
|
||||
|
||||
public MappingConfig(DatabaseConfig database, List<SheetMapping> mappings, MigrationSettings settings, ApiConfig dsolFactory) {
|
||||
this.database = database;
|
||||
this.mappings = mappings;
|
||||
this.settings = settings;
|
||||
this.dsolFactory = dsolFactory;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public DatabaseConfig getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public void setDatabase(DatabaseConfig database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public List<SheetMapping> getMappings() {
|
||||
return mappings;
|
||||
}
|
||||
|
||||
public void setMappings(List<SheetMapping> mappings) {
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
public MigrationSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void setSettings(MigrationSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public ApiConfig getDsolFactory() {
|
||||
return dsolFactory;
|
||||
}
|
||||
|
||||
public void setDsolFactory(ApiConfig dsolFactory) {
|
||||
this.dsolFactory = dsolFactory;
|
||||
}
|
||||
}
|
||||
72
src/main/java/com/excelaccess/model/MigrationSettings.java
Normal file
72
src/main/java/com/excelaccess/model/MigrationSettings.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package com.excelaccess.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class MigrationSettings {
|
||||
@JsonProperty("skipEmptyRows")
|
||||
private boolean skipEmptyRows;
|
||||
|
||||
@JsonProperty("skipHeaderRow")
|
||||
private boolean skipHeaderRow;
|
||||
|
||||
@JsonProperty("batchSize")
|
||||
private int batchSize;
|
||||
|
||||
@JsonProperty("createTableIfNotExists")
|
||||
private boolean createTableIfNotExists;
|
||||
|
||||
@JsonProperty("id_curator")
|
||||
private int idCurator;
|
||||
|
||||
// Constructors
|
||||
public MigrationSettings() {}
|
||||
|
||||
public MigrationSettings(boolean skipEmptyRows, boolean skipHeaderRow, int batchSize, boolean createTableIfNotExists, int idCurator) {
|
||||
this.skipEmptyRows = skipEmptyRows;
|
||||
this.skipHeaderRow = skipHeaderRow;
|
||||
this.batchSize = batchSize;
|
||||
this.createTableIfNotExists = createTableIfNotExists;
|
||||
this.idCurator = idCurator;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public boolean isSkipEmptyRows() {
|
||||
return skipEmptyRows;
|
||||
}
|
||||
|
||||
public void setSkipEmptyRows(boolean skipEmptyRows) {
|
||||
this.skipEmptyRows = skipEmptyRows;
|
||||
}
|
||||
|
||||
public boolean isSkipHeaderRow() {
|
||||
return skipHeaderRow;
|
||||
}
|
||||
|
||||
public void setSkipHeaderRow(boolean skipHeaderRow) {
|
||||
this.skipHeaderRow = skipHeaderRow;
|
||||
}
|
||||
|
||||
public int getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public boolean isCreateTableIfNotExists() {
|
||||
return createTableIfNotExists;
|
||||
}
|
||||
|
||||
public void setCreateTableIfNotExists(boolean createTableIfNotExists) {
|
||||
this.createTableIfNotExists = createTableIfNotExists;
|
||||
}
|
||||
|
||||
public int getIdCurator() {
|
||||
return idCurator;
|
||||
}
|
||||
|
||||
public void setIdCurator(int idCurator) {
|
||||
this.idCurator = idCurator;
|
||||
}
|
||||
}
|
||||
49
src/main/java/com/excelaccess/model/SheetMapping.java
Normal file
49
src/main/java/com/excelaccess/model/SheetMapping.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.excelaccess.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
public class SheetMapping {
|
||||
@JsonProperty("excelSheet")
|
||||
private String excelSheet;
|
||||
|
||||
@JsonProperty("accessTable")
|
||||
private String accessTable;
|
||||
|
||||
@JsonProperty("columnMappings")
|
||||
private List<ColumnMapping> columnMappings;
|
||||
|
||||
// Constructors
|
||||
public SheetMapping() {}
|
||||
|
||||
public SheetMapping(String excelSheet, String accessTable, List<ColumnMapping> columnMappings) {
|
||||
this.excelSheet = excelSheet;
|
||||
this.accessTable = accessTable;
|
||||
this.columnMappings = columnMappings;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getExcelSheet() {
|
||||
return excelSheet;
|
||||
}
|
||||
|
||||
public void setExcelSheet(String excelSheet) {
|
||||
this.excelSheet = excelSheet;
|
||||
}
|
||||
|
||||
public String getAccessTable() {
|
||||
return accessTable;
|
||||
}
|
||||
|
||||
public void setAccessTable(String accessTable) {
|
||||
this.accessTable = accessTable;
|
||||
}
|
||||
|
||||
public List<ColumnMapping> getColumnMappings() {
|
||||
return columnMappings;
|
||||
}
|
||||
|
||||
public void setColumnMappings(List<ColumnMapping> columnMappings) {
|
||||
this.columnMappings = columnMappings;
|
||||
}
|
||||
}
|
||||
268
src/main/java/com/excelaccess/service/AccessService.java
Normal file
268
src/main/java/com/excelaccess/service/AccessService.java
Normal file
@@ -0,0 +1,268 @@
|
||||
package com.excelaccess.service;
|
||||
|
||||
import com.excelaccess.model.ColumnMapping;
|
||||
import com.excelaccess.model.SheetMapping;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AccessService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AccessService.class);
|
||||
|
||||
private Connection connection;
|
||||
private boolean isStopped = false;
|
||||
|
||||
public void connect(String connectionString) throws SQLException {
|
||||
try {
|
||||
Class.forName("net.ucanaccess.jdbc.UcanaccessDriver");
|
||||
connection = DriverManager.getConnection(connectionString);
|
||||
logger.info("Connected to Access database: {}", connectionString);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new SQLException("UCanAccess driver not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() throws SQLException {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
logger.info("Disconnected from Access database");
|
||||
}
|
||||
}
|
||||
|
||||
public void createTableIfNotExists(SheetMapping mapping) throws SQLException {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
throw new SQLException("Not connected to database");
|
||||
}
|
||||
|
||||
String tableName = mapping.getAccessTable();
|
||||
String createTableSQL = buildCreateTableSQL(mapping);
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
// Check if table exists
|
||||
if (!tableExists(tableName)) {
|
||||
logger.info("Creating table: {}", tableName);
|
||||
statement.execute(createTableSQL);
|
||||
logger.info("Table '{}' created successfully", tableName);
|
||||
} else {
|
||||
logger.info("Table '{}' already exists", tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void insertData(SheetMapping mapping, List<Map<String, Object>> data) throws SQLException {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
throw new SQLException("Not connected to database");
|
||||
}
|
||||
|
||||
if (data.isEmpty()) {
|
||||
logger.info("No data to insert for table: {}", mapping.getAccessTable());
|
||||
return;
|
||||
}
|
||||
|
||||
String tableName = mapping.getAccessTable();
|
||||
String insertSQL = buildInsertSQL(mapping);
|
||||
|
||||
logger.info("Inserting {} rows into table: {}", data.size(), tableName);
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement(insertSQL)) {
|
||||
connection.setAutoCommit(false);
|
||||
|
||||
int batchCount = 0;
|
||||
for (Map<String, Object> row : data) {
|
||||
if (isStopped) {
|
||||
logger.info("Migration stopped by user");
|
||||
break;
|
||||
}
|
||||
|
||||
setStatementParameters(statement, mapping, row);
|
||||
statement.addBatch();
|
||||
batchCount++;
|
||||
|
||||
if (batchCount % 1000 == 0) {
|
||||
statement.executeBatch();
|
||||
connection.commit();
|
||||
logger.info("Inserted {} rows into table: {}", batchCount, tableName);
|
||||
}
|
||||
}
|
||||
|
||||
if (batchCount % 1000 != 0) {
|
||||
statement.executeBatch();
|
||||
}
|
||||
|
||||
connection.commit();
|
||||
logger.info("Successfully inserted {} rows into table: {}", batchCount, tableName);
|
||||
|
||||
} catch (SQLException e) {
|
||||
connection.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearTable(String tableName) throws SQLException {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
throw new SQLException("Not connected to database");
|
||||
}
|
||||
|
||||
String deleteSQL = "DELETE FROM " + tableName;
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
int deletedRows = statement.executeUpdate(deleteSQL);
|
||||
logger.info("Cleared {} rows from table: {}", deletedRows, tableName);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean tableExists(String tableName) throws SQLException {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
throw new SQLException("Not connected to database");
|
||||
}
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
ResultSet rs = statement.executeQuery(
|
||||
"SELECT COUNT(*) FROM MSysObjects WHERE Name='" + tableName + "' AND Type=1"
|
||||
);
|
||||
return rs.next() && rs.getInt(1) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopMigration() {
|
||||
isStopped = true;
|
||||
logger.info("Migration stop requested");
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
private String buildCreateTableSQL(SheetMapping mapping) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("CREATE TABLE ").append(mapping.getAccessTable()).append(" (");
|
||||
|
||||
for (int i = 0; i < mapping.getColumnMappings().size(); i++) {
|
||||
ColumnMapping column = mapping.getColumnMappings().get(i);
|
||||
if (i > 0) {
|
||||
sql.append(", ");
|
||||
}
|
||||
sql.append(column.getAccessColumn()).append(" ").append(mapDataType(column.getDataType()));
|
||||
}
|
||||
|
||||
sql.append(")");
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private String buildInsertSQL(SheetMapping mapping) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("INSERT INTO ").append(mapping.getAccessTable()).append(" (");
|
||||
|
||||
for (int i = 0; i < mapping.getColumnMappings().size(); i++) {
|
||||
ColumnMapping column = mapping.getColumnMappings().get(i);
|
||||
if (i > 0) {
|
||||
sql.append(", ");
|
||||
}
|
||||
sql.append(column.getAccessColumn());
|
||||
}
|
||||
|
||||
sql.append(") VALUES (");
|
||||
|
||||
for (int i = 0; i < mapping.getColumnMappings().size(); i++) {
|
||||
if (i > 0) {
|
||||
sql.append(", ");
|
||||
}
|
||||
sql.append("?");
|
||||
}
|
||||
|
||||
sql.append(")");
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
private void setStatementParameters(PreparedStatement statement, SheetMapping mapping, Map<String, Object> row) throws SQLException {
|
||||
for (int i = 0; i < mapping.getColumnMappings().size(); i++) {
|
||||
ColumnMapping column = mapping.getColumnMappings().get(i);
|
||||
Object value = row.get(column.getAccessColumn());
|
||||
|
||||
if (value == null) {
|
||||
statement.setNull(i + 1, Types.NULL);
|
||||
} else {
|
||||
setParameterByType(statement, i + 1, value, column.getDataType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setParameterByType(PreparedStatement statement, int index, Object value, String dataType) throws SQLException {
|
||||
switch (dataType.toUpperCase()) {
|
||||
case "INTEGER":
|
||||
case "INT":
|
||||
if (value instanceof Number) {
|
||||
statement.setInt(index, ((Number) value).intValue());
|
||||
} else {
|
||||
statement.setInt(index, Integer.parseInt(value.toString()));
|
||||
}
|
||||
break;
|
||||
|
||||
case "DECIMAL":
|
||||
case "DOUBLE":
|
||||
case "FLOAT":
|
||||
if (value instanceof Number) {
|
||||
statement.setDouble(index, ((Number) value).doubleValue());
|
||||
} else {
|
||||
statement.setDouble(index, Double.parseDouble(value.toString()));
|
||||
}
|
||||
break;
|
||||
|
||||
case "VARCHAR":
|
||||
case "STRING":
|
||||
case "TEXT":
|
||||
statement.setString(index, value.toString());
|
||||
break;
|
||||
|
||||
case "BOOLEAN":
|
||||
case "BOOL":
|
||||
if (value instanceof Boolean) {
|
||||
statement.setBoolean(index, (Boolean) value);
|
||||
} else {
|
||||
statement.setBoolean(index, Boolean.parseBoolean(value.toString()));
|
||||
}
|
||||
break;
|
||||
|
||||
case "DATE":
|
||||
if (value instanceof java.util.Date) {
|
||||
statement.setTimestamp(index, new Timestamp(((java.util.Date) value).getTime()));
|
||||
} else {
|
||||
statement.setString(index, value.toString());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
statement.setString(index, value.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private String mapDataType(String dataType) {
|
||||
switch (dataType.toUpperCase()) {
|
||||
case "INTEGER":
|
||||
case "INT":
|
||||
return "INTEGER";
|
||||
case "DECIMAL":
|
||||
case "DOUBLE":
|
||||
case "FLOAT":
|
||||
return "DOUBLE";
|
||||
case "VARCHAR":
|
||||
case "STRING":
|
||||
case "TEXT":
|
||||
return "VARCHAR(255)";
|
||||
case "BOOLEAN":
|
||||
case "BOOL":
|
||||
return "BIT";
|
||||
case "DATE":
|
||||
return "DATETIME";
|
||||
default:
|
||||
return "VARCHAR(255)";
|
||||
}
|
||||
}
|
||||
}
|
||||
198
src/main/java/com/excelaccess/service/ConfigService.java
Normal file
198
src/main/java/com/excelaccess/service/ConfigService.java
Normal file
@@ -0,0 +1,198 @@
|
||||
package com.excelaccess.service;
|
||||
|
||||
import com.excelaccess.model.MappingConfig;
|
||||
import com.excelaccess.model.DatabaseConfig;
|
||||
import com.excelaccess.model.MigrationSettings;
|
||||
import com.excelaccess.model.ApiConfig;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ConfigService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigService.class);
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public ConfigService() {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
public MappingConfig loadConfig(String configFilePath) throws IOException {
|
||||
File configFile = new File(configFilePath);
|
||||
|
||||
if (!configFile.exists()) {
|
||||
throw new IOException("Configuration file not found: " + configFilePath);
|
||||
}
|
||||
|
||||
if (!configFile.canRead()) {
|
||||
throw new IOException("Cannot read configuration file: " + configFilePath);
|
||||
}
|
||||
|
||||
logger.info("Loading configuration from: {}", configFilePath);
|
||||
|
||||
try {
|
||||
MappingConfig config = objectMapper.readValue(configFile, MappingConfig.class);
|
||||
validateConfig(config);
|
||||
logger.info("Configuration loaded successfully");
|
||||
return config;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error loading configuration", e);
|
||||
throw new IOException("Failed to parse configuration file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public MappingConfig loadConfigFromHomeDirectory() throws IOException {
|
||||
String homeDir = System.getProperty("user.home");
|
||||
String configFileName = "excel-access-migration-config.json";
|
||||
String configFilePath = homeDir + File.separator + configFileName;
|
||||
|
||||
File configFile = new File(configFilePath);
|
||||
|
||||
if (!configFile.exists()) {
|
||||
logger.info("Configuration file not found in home directory, creating default: {}", configFilePath);
|
||||
createDefaultConfig(configFilePath);
|
||||
}
|
||||
|
||||
return loadConfig(configFilePath);
|
||||
}
|
||||
|
||||
private void createDefaultConfig(String configFilePath) throws IOException {
|
||||
MappingConfig defaultConfig = new MappingConfig();
|
||||
|
||||
// Set default database config
|
||||
DatabaseConfig dbConfig = new DatabaseConfig("");
|
||||
defaultConfig.setDatabase(dbConfig);
|
||||
|
||||
// Set default mappings (empty for now)
|
||||
defaultConfig.setMappings(new ArrayList<>());
|
||||
|
||||
// Set default settings
|
||||
MigrationSettings settings = new MigrationSettings();
|
||||
settings.setSkipEmptyRows(true);
|
||||
settings.setSkipHeaderRow(true);
|
||||
settings.setBatchSize(1000);
|
||||
settings.setCreateTableIfNotExists(true);
|
||||
settings.setIdCurator(1);
|
||||
defaultConfig.setSettings(settings);
|
||||
|
||||
// Set default API config
|
||||
ApiConfig apiConfig = new ApiConfig();
|
||||
apiConfig.setBaseUrl("");
|
||||
apiConfig.setApiGetLabelId("");
|
||||
defaultConfig.setDsolFactory(apiConfig);
|
||||
|
||||
saveConfig(defaultConfig, configFilePath);
|
||||
}
|
||||
|
||||
public void updateAccessFilePath(String accessFilePath) throws IOException {
|
||||
String homeDir = System.getProperty("user.home");
|
||||
String configFileName = "excel-access-migration-config.json";
|
||||
String configFilePath = homeDir + File.separator + configFileName;
|
||||
|
||||
MappingConfig config = loadConfigFromHomeDirectory();
|
||||
String connectionString = "jdbc:ucanaccess://" + accessFilePath;
|
||||
config.getDatabase().setConnectionString(connectionString);
|
||||
|
||||
saveConfig(config, configFilePath);
|
||||
logger.info("Updated Access connection string in configuration: {}", connectionString);
|
||||
}
|
||||
|
||||
public String getAccessFilePathFromConfig() throws IOException {
|
||||
MappingConfig config = loadConfigFromHomeDirectory();
|
||||
String connectionString = config.getDatabase().getConnectionString();
|
||||
|
||||
if (connectionString == null || connectionString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract file path from connection string: jdbc:ucanaccess://path/to/file.accdb
|
||||
if (connectionString.startsWith("jdbc:ucanaccess://")) {
|
||||
return connectionString.substring("jdbc:ucanaccess://".length());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public MappingConfig loadDefaultConfig() throws IOException {
|
||||
String defaultConfigPath = "src/main/resources/mapping-config.json";
|
||||
return loadConfig(defaultConfigPath);
|
||||
}
|
||||
|
||||
public void saveConfig(MappingConfig config, String configFilePath) throws IOException {
|
||||
File configFile = new File(configFilePath);
|
||||
|
||||
// Create parent directories if they don't exist
|
||||
File parentDir = configFile.getParentFile();
|
||||
if (parentDir != null && !parentDir.exists()) {
|
||||
parentDir.mkdirs();
|
||||
}
|
||||
|
||||
logger.info("Saving configuration to: {}", configFilePath);
|
||||
|
||||
try {
|
||||
objectMapper.writerWithDefaultPrettyPrinter().writeValue(configFile, config);
|
||||
logger.info("Configuration saved successfully");
|
||||
} catch (Exception e) {
|
||||
logger.error("Error saving configuration", e);
|
||||
throw new IOException("Failed to save configuration file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateConfig(MappingConfig config) throws IOException {
|
||||
if (config == null) {
|
||||
throw new IOException("Configuration is null");
|
||||
}
|
||||
|
||||
if (config.getDatabase() == null) {
|
||||
throw new IOException("Database configuration is missing");
|
||||
}
|
||||
|
||||
if (config.getMappings() != null) {
|
||||
for (int i = 0; i < config.getMappings().size(); i++) {
|
||||
var mapping = config.getMappings().get(i);
|
||||
|
||||
if (mapping.getExcelSheet() == null || mapping.getExcelSheet().trim().isEmpty()) {
|
||||
throw new IOException("Excel sheet name is missing for mapping " + i);
|
||||
}
|
||||
|
||||
if (mapping.getAccessTable() == null || mapping.getAccessTable().trim().isEmpty()) {
|
||||
throw new IOException("Access table name is missing for mapping " + i);
|
||||
}
|
||||
|
||||
if (mapping.getColumnMappings() == null || mapping.getColumnMappings().isEmpty()) {
|
||||
throw new IOException("Column mappings are missing for mapping " + i);
|
||||
}
|
||||
|
||||
for (int j = 0; j < mapping.getColumnMappings().size(); j++) {
|
||||
var columnMapping = mapping.getColumnMappings().get(j);
|
||||
|
||||
if (columnMapping.getExcelColumn() == null || columnMapping.getExcelColumn().trim().isEmpty()) {
|
||||
throw new IOException("Excel column is missing for mapping " + i + ", column " + j);
|
||||
}
|
||||
|
||||
if (columnMapping.getAccessColumn() == null || columnMapping.getAccessColumn().trim().isEmpty()) {
|
||||
throw new IOException("Access column is missing for mapping " + i + ", column " + j);
|
||||
}
|
||||
|
||||
if (columnMapping.getDataType() == null || columnMapping.getDataType().trim().isEmpty()) {
|
||||
throw new IOException("Data type is missing for mapping " + i + ", column " + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getSettings() == null) {
|
||||
throw new IOException("Migration settings are missing");
|
||||
}
|
||||
|
||||
if (config.getSettings().getBatchSize() <= 0) {
|
||||
throw new IOException("Batch size must be greater than 0");
|
||||
}
|
||||
|
||||
logger.info("Configuration validation passed");
|
||||
}
|
||||
}
|
||||
374
src/main/java/com/excelaccess/service/ExcelAnalysisService.java
Normal file
374
src/main/java/com/excelaccess/service/ExcelAnalysisService.java
Normal file
@@ -0,0 +1,374 @@
|
||||
package com.excelaccess.service;
|
||||
|
||||
import com.excelaccess.model.MappingConfig;
|
||||
import com.excelaccess.model.SheetMapping;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ExcelAnalysisService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExcelAnalysisService.class);
|
||||
|
||||
public AnalysisResult analyzeExcelFile(String filePath) throws IOException {
|
||||
logger.info("Starting analysis of Excel file: {}", filePath);
|
||||
|
||||
AnalysisResult result = new AnalysisResult();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(filePath);
|
||||
Workbook workbook = createWorkbook(fis, filePath)) {
|
||||
|
||||
// Analyze Product Cards sheet
|
||||
Sheet productCardsSheet = workbook.getSheet("Карточки товара");
|
||||
if (productCardsSheet != null) {
|
||||
result.setProductCards(analyzeProductCards(productCardsSheet));
|
||||
} else {
|
||||
logger.warn("Sheet 'Карточки товара' not found");
|
||||
}
|
||||
|
||||
// Analyze Transactions sheet
|
||||
Sheet transactionsSheet = workbook.getSheet("Транзакции");
|
||||
if (transactionsSheet != null) {
|
||||
result.setTransactions(analyzeTransactions(transactionsSheet));
|
||||
} else {
|
||||
logger.warn("Sheet 'Транзакции' not found");
|
||||
}
|
||||
|
||||
// Perform cross-analysis
|
||||
if (result.getProductCards() != null && result.getTransactions() != null) {
|
||||
result.setCrossAnalysis(performCrossAnalysis(result.getProductCards(), result.getTransactions()));
|
||||
}
|
||||
|
||||
logger.info("Excel analysis completed successfully");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<ProductCard> analyzeProductCards(Sheet sheet) {
|
||||
List<ProductCard> cards = new ArrayList<>();
|
||||
|
||||
// Skip header row
|
||||
logger.debug("Reading product cards from sheet, last row: {}", sheet.getLastRowNum());
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
logger.debug("Row {} is null, skipping", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
ProductCard card = new ProductCard();
|
||||
card.setName(getCellValueAsString(row.getCell(0))); // Наименование
|
||||
card.setSupplierName(getCellValueAsString(row.getCell(1))); // Наименование у поставщика
|
||||
card.setProject(getCellValueAsString(row.getCell(2))); // Проект
|
||||
card.setLocation(getCellValueAsString(row.getCell(3))); // Местоположение
|
||||
card.setCategory(getCellValueAsString(row.getCell(4))); // Категория
|
||||
card.setArticleNumbers(getCellValueAsString(row.getCell(5))); // Артикулы
|
||||
card.setNote(getCellValueAsString(row.getCell(6))); // Примечание
|
||||
|
||||
logger.debug("Row {}: name='{}', supplier='{}', project='{}'",
|
||||
i, card.getName(), card.getSupplierName(), card.getProject());
|
||||
|
||||
if (card.getName() != null && !card.getName().trim().isEmpty()) {
|
||||
cards.add(card);
|
||||
logger.debug("Added product card: {}", card.getName());
|
||||
} else {
|
||||
logger.debug("Skipped empty product card at row {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Analyzed {} product cards", cards.size());
|
||||
for (ProductCard card : cards) {
|
||||
logger.debug("Product card: {}", card.getName());
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
private List<Transaction> analyzeTransactions(Sheet sheet) {
|
||||
List<Transaction> transactions = new ArrayList<>();
|
||||
|
||||
// Skip header row
|
||||
logger.debug("Reading transactions from sheet, last row: {}", sheet.getLastRowNum());
|
||||
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||
Row row = sheet.getRow(i);
|
||||
if (row == null) {
|
||||
logger.debug("Row {} is null, skipping", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
Transaction transaction = new Transaction();
|
||||
transaction.setName(getCellValueAsString(row.getCell(0))); // Наименование
|
||||
transaction.setIncome(getCellValueAsInteger(row.getCell(1))); // Приход
|
||||
transaction.setWho(getCellValueAsString(row.getCell(2))); // Кто
|
||||
transaction.setDate(getCellValueAsDate(row.getCell(3))); // Дата
|
||||
transaction.setNote(getCellValueAsString(row.getCell(4))); // Примечание
|
||||
transaction.setInvoiceNumber(getCellValueAsString(row.getCell(5))); // № накладной
|
||||
transaction.setTaskNumber(getCellValueAsString(row.getCell(6))); // задача №
|
||||
transaction.setResponsible(getCellValueAsString(row.getCell(7))); // Ответственный
|
||||
transaction.setWarehouse(getCellValueAsString(row.getCell(8))); // Склад
|
||||
|
||||
logger.debug("Row {}: name='{}', income={}, who='{}'",
|
||||
i, transaction.getName(), transaction.getIncome(), transaction.getWho());
|
||||
|
||||
if (transaction.getName() != null && !transaction.getName().trim().isEmpty()) {
|
||||
transactions.add(transaction);
|
||||
logger.debug("Added transaction: {}", transaction.getName());
|
||||
} else {
|
||||
logger.debug("Skipped empty transaction at row {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Analyzed {} transactions", transactions.size());
|
||||
for (Transaction transaction : transactions) {
|
||||
logger.debug("Transaction: {}", transaction.getName());
|
||||
}
|
||||
return transactions;
|
||||
}
|
||||
|
||||
private CrossAnalysis performCrossAnalysis(List<ProductCard> cards, List<Transaction> transactions) {
|
||||
CrossAnalysis analysis = new CrossAnalysis();
|
||||
|
||||
// Find products with transactions
|
||||
Set<String> productNames = cards.stream()
|
||||
.map(ProductCard::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> transactionProductNames = transactions.stream()
|
||||
.map(Transaction::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Products with transactions
|
||||
Set<String> productsWithTransactions = new HashSet<>(productNames);
|
||||
productsWithTransactions.retainAll(transactionProductNames);
|
||||
analysis.setProductsWithTransactions(productsWithTransactions);
|
||||
|
||||
// Products without transactions
|
||||
Set<String> productsWithoutTransactions = new HashSet<>(productNames);
|
||||
productsWithoutTransactions.removeAll(transactionProductNames);
|
||||
analysis.setProductsWithoutTransactions(productsWithoutTransactions);
|
||||
|
||||
// Transactions for unknown products
|
||||
Set<String> unknownProductTransactions = new HashSet<>(transactionProductNames);
|
||||
unknownProductTransactions.removeAll(productNames);
|
||||
analysis.setUnknownProductTransactions(unknownProductTransactions);
|
||||
|
||||
// Calculate total income by product
|
||||
Map<String, Integer> totalIncomeByProduct = transactions.stream()
|
||||
.filter(t -> t.getIncome() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
Transaction::getName,
|
||||
Collectors.summingInt(Transaction::getIncome)
|
||||
));
|
||||
analysis.setTotalIncomeByProduct(totalIncomeByProduct);
|
||||
|
||||
// Calculate total income by category
|
||||
Map<String, Integer> totalIncomeByCategory = new HashMap<>();
|
||||
for (ProductCard card : cards) {
|
||||
if (card.getCategory() != null && productsWithTransactions.contains(card.getName())) {
|
||||
Integer income = totalIncomeByProduct.get(card.getName());
|
||||
if (income != null) {
|
||||
totalIncomeByCategory.merge(card.getCategory(), income, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
analysis.setTotalIncomeByCategory(totalIncomeByCategory);
|
||||
|
||||
logger.info("Cross analysis completed: {} products with transactions, {} without, {} unknown transactions",
|
||||
productsWithTransactions.size(), productsWithoutTransactions.size(), unknownProductTransactions.size());
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
private Workbook createWorkbook(FileInputStream fis, String filePath) throws IOException {
|
||||
if (filePath.toLowerCase().endsWith(".xlsx")) {
|
||||
return new XSSFWorkbook(fis);
|
||||
} else if (filePath.toLowerCase().endsWith(".xls")) {
|
||||
return new HSSFWorkbook(fis);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported file format. Only .xlsx and .xls files are supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private String getCellValueAsString(Cell cell) {
|
||||
if (cell == null) return "";
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
return String.valueOf((long) cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
try {
|
||||
return getCellValueAsString(cell.getCachedFormulaResultType(), cell);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getCellValueAsString(CellType cellType, Cell cell) {
|
||||
switch (cellType) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
return String.valueOf((long) cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getCellValueAsInteger(Cell cell) {
|
||||
if (cell == null) return null;
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case NUMERIC:
|
||||
return (int) cell.getNumericCellValue();
|
||||
case STRING:
|
||||
try {
|
||||
return Integer.parseInt(cell.getStringCellValue());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate getCellValueAsDate(Cell cell) {
|
||||
if (cell == null) return null;
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toInstant()
|
||||
.atZone(java.time.ZoneId.systemDefault())
|
||||
.toLocalDate();
|
||||
}
|
||||
return null;
|
||||
case STRING:
|
||||
try {
|
||||
return LocalDate.parse(cell.getStringCellValue(), DateTimeFormatter.ofPattern("dd.MM.yyyy"));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Data classes
|
||||
public static class AnalysisResult {
|
||||
private List<ProductCard> productCards;
|
||||
private List<Transaction> transactions;
|
||||
private CrossAnalysis crossAnalysis;
|
||||
|
||||
// Getters and setters
|
||||
public List<ProductCard> getProductCards() { return productCards; }
|
||||
public void setProductCards(List<ProductCard> productCards) { this.productCards = productCards; }
|
||||
public List<Transaction> getTransactions() { return transactions; }
|
||||
public void setTransactions(List<Transaction> transactions) { this.transactions = transactions; }
|
||||
public CrossAnalysis getCrossAnalysis() { return crossAnalysis; }
|
||||
public void setCrossAnalysis(CrossAnalysis crossAnalysis) { this.crossAnalysis = crossAnalysis; }
|
||||
}
|
||||
|
||||
public static class ProductCard {
|
||||
private String name;
|
||||
private String supplierName;
|
||||
private String project;
|
||||
private String location;
|
||||
private String category;
|
||||
private String articleNumbers;
|
||||
private String note;
|
||||
|
||||
// Getters and setters
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getSupplierName() { return supplierName; }
|
||||
public void setSupplierName(String supplierName) { this.supplierName = supplierName; }
|
||||
public String getProject() { return project; }
|
||||
public void setProject(String project) { this.project = project; }
|
||||
public String getLocation() { return location; }
|
||||
public void setLocation(String location) { this.location = location; }
|
||||
public String getCategory() { return category; }
|
||||
public void setCategory(String category) { this.category = category; }
|
||||
public String getArticleNumbers() { return articleNumbers; }
|
||||
public void setArticleNumbers(String articleNumbers) { this.articleNumbers = articleNumbers; }
|
||||
public String getNote() { return note; }
|
||||
public void setNote(String note) { this.note = note; }
|
||||
}
|
||||
|
||||
public static class Transaction {
|
||||
private String name;
|
||||
private Integer income;
|
||||
private String who;
|
||||
private LocalDate date;
|
||||
private String note;
|
||||
private String invoiceNumber;
|
||||
private String taskNumber;
|
||||
private String responsible;
|
||||
private String warehouse;
|
||||
|
||||
// Getters and setters
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public Integer getIncome() { return income; }
|
||||
public void setIncome(Integer income) { this.income = income; }
|
||||
public String getWho() { return who; }
|
||||
public void setWho(String who) { this.who = who; }
|
||||
public LocalDate getDate() { return date; }
|
||||
public void setDate(LocalDate date) { this.date = date; }
|
||||
public String getNote() { return note; }
|
||||
public void setNote(String note) { this.note = note; }
|
||||
public String getInvoiceNumber() { return invoiceNumber; }
|
||||
public void setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; }
|
||||
public String getTaskNumber() { return taskNumber; }
|
||||
public void setTaskNumber(String taskNumber) { this.taskNumber = taskNumber; }
|
||||
public String getResponsible() { return responsible; }
|
||||
public void setResponsible(String responsible) { this.responsible = responsible; }
|
||||
public String getWarehouse() { return warehouse; }
|
||||
public void setWarehouse(String warehouse) { this.warehouse = warehouse; }
|
||||
}
|
||||
|
||||
public static class CrossAnalysis {
|
||||
private Set<String> productsWithTransactions;
|
||||
private Set<String> productsWithoutTransactions;
|
||||
private Set<String> unknownProductTransactions;
|
||||
private Map<String, Integer> totalIncomeByProduct;
|
||||
private Map<String, Integer> totalIncomeByCategory;
|
||||
|
||||
// Getters and setters
|
||||
public Set<String> getProductsWithTransactions() { return productsWithTransactions; }
|
||||
public void setProductsWithTransactions(Set<String> productsWithTransactions) { this.productsWithTransactions = productsWithTransactions; }
|
||||
public Set<String> getProductsWithoutTransactions() { return productsWithoutTransactions; }
|
||||
public void setProductsWithoutTransactions(Set<String> productsWithoutTransactions) { this.productsWithoutTransactions = productsWithoutTransactions; }
|
||||
public Set<String> getUnknownProductTransactions() { return unknownProductTransactions; }
|
||||
public void setUnknownProductTransactions(Set<String> unknownProductTransactions) { this.unknownProductTransactions = unknownProductTransactions; }
|
||||
public Map<String, Integer> getTotalIncomeByProduct() { return totalIncomeByProduct; }
|
||||
public void setTotalIncomeByProduct(Map<String, Integer> totalIncomeByProduct) { this.totalIncomeByProduct = totalIncomeByProduct; }
|
||||
public Map<String, Integer> getTotalIncomeByCategory() { return totalIncomeByCategory; }
|
||||
public void setTotalIncomeByCategory(Map<String, Integer> totalIncomeByCategory) { this.totalIncomeByCategory = totalIncomeByCategory; }
|
||||
}
|
||||
}
|
||||
271
src/main/java/com/excelaccess/service/ExcelService.java
Normal file
271
src/main/java/com/excelaccess/service/ExcelService.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package com.excelaccess.service;
|
||||
|
||||
import com.excelaccess.model.ColumnMapping;
|
||||
import com.excelaccess.model.SheetMapping;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class ExcelService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExcelService.class);
|
||||
|
||||
public List<Map<String, Object>> readSheetData(String filePath, SheetMapping mapping) throws IOException {
|
||||
List<Map<String, Object>> data = new ArrayList<>();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(filePath);
|
||||
Workbook workbook = createWorkbook(fis, filePath)) {
|
||||
|
||||
Sheet sheet = workbook.getSheet(mapping.getExcelSheet());
|
||||
if (sheet == null) {
|
||||
throw new IllegalArgumentException("Sheet '" + mapping.getExcelSheet() + "' not found in Excel file");
|
||||
}
|
||||
|
||||
logger.info("Reading data from sheet: {}", mapping.getExcelSheet());
|
||||
|
||||
int startRow = 0;
|
||||
// Skip header row if configured
|
||||
if (mapping.getColumnMappings() != null && !mapping.getColumnMappings().isEmpty()) {
|
||||
startRow = 1; // Assuming first row is header
|
||||
}
|
||||
|
||||
for (Row row : sheet) {
|
||||
if (row.getRowNum() < startRow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> rowData = new HashMap<>();
|
||||
boolean hasData = false;
|
||||
|
||||
for (ColumnMapping columnMapping : mapping.getColumnMappings()) {
|
||||
Cell cell = row.getCell(getColumnIndex(columnMapping.getExcelColumn()));
|
||||
Object cellValue = getCellValue(cell, columnMapping.getDataType());
|
||||
|
||||
if (cellValue != null && !cellValue.toString().trim().isEmpty()) {
|
||||
hasData = true;
|
||||
}
|
||||
|
||||
rowData.put(columnMapping.getAccessColumn(), cellValue);
|
||||
}
|
||||
|
||||
if (hasData) {
|
||||
data.add(rowData);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Read {} rows from sheet: {}", data.size(), mapping.getExcelSheet());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public List<String> getSheetNames(String filePath) throws IOException {
|
||||
List<String> sheetNames = new ArrayList<>();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(filePath);
|
||||
Workbook workbook = createWorkbook(fis, filePath)) {
|
||||
|
||||
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
|
||||
sheetNames.add(workbook.getSheetName(i));
|
||||
}
|
||||
}
|
||||
|
||||
return sheetNames;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getColumnHeaders(String filePath, String sheetName) throws IOException {
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(filePath);
|
||||
Workbook workbook = createWorkbook(fis, filePath)) {
|
||||
|
||||
Sheet sheet = workbook.getSheet(sheetName);
|
||||
if (sheet == null) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
Row headerRow = sheet.getRow(0);
|
||||
if (headerRow != null) {
|
||||
List<String> columnHeaders = new ArrayList<>();
|
||||
for (Cell cell : headerRow) {
|
||||
String header = getCellValueAsString(cell);
|
||||
columnHeaders.add(header);
|
||||
}
|
||||
headers.put(sheetName, columnHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
private Workbook createWorkbook(FileInputStream fis, String filePath) throws IOException {
|
||||
if (filePath.toLowerCase().endsWith(".xlsx")) {
|
||||
return new XSSFWorkbook(fis);
|
||||
} else if (filePath.toLowerCase().endsWith(".xls")) {
|
||||
return new HSSFWorkbook(fis);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported file format. Only .xlsx and .xls files are supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private int getColumnIndex(String columnLetter) {
|
||||
int result = 0;
|
||||
for (int i = 0; i < columnLetter.length(); i++) {
|
||||
result = result * 26 + (columnLetter.charAt(i) - 'A' + 1);
|
||||
}
|
||||
return result - 1; // Convert to 0-based index
|
||||
}
|
||||
|
||||
private Object getCellValue(Cell cell, String dataType) {
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
String stringValue = cell.getStringCellValue();
|
||||
return convertToDataType(stringValue, dataType);
|
||||
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue();
|
||||
} else {
|
||||
double numericValue = cell.getNumericCellValue();
|
||||
return convertToDataType(numericValue, dataType);
|
||||
}
|
||||
|
||||
case BOOLEAN:
|
||||
return cell.getBooleanCellValue();
|
||||
|
||||
case FORMULA:
|
||||
try {
|
||||
return getCellValue(cell.getCachedFormulaResultType(), cell, dataType);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error evaluating formula in cell: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getCellValue(CellType cellType, Cell cell, String dataType) {
|
||||
switch (cellType) {
|
||||
case STRING:
|
||||
return convertToDataType(cell.getStringCellValue(), dataType);
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue();
|
||||
} else {
|
||||
return convertToDataType(cell.getNumericCellValue(), dataType);
|
||||
}
|
||||
case BOOLEAN:
|
||||
return cell.getBooleanCellValue();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getCellValueAsString(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
return String.valueOf(cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
case FORMULA:
|
||||
try {
|
||||
return getCellValueAsString(cell.getCachedFormulaResultType(), cell);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getCellValueAsString(CellType cellType, Cell cell) {
|
||||
switch (cellType) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue();
|
||||
case NUMERIC:
|
||||
if (DateUtil.isCellDateFormatted(cell)) {
|
||||
return cell.getDateCellValue().toString();
|
||||
} else {
|
||||
return String.valueOf(cell.getNumericCellValue());
|
||||
}
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertToDataType(Object value, String dataType) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (dataType.toUpperCase()) {
|
||||
case "INTEGER":
|
||||
case "INT":
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
} else if (value instanceof String) {
|
||||
return Integer.parseInt((String) value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "DECIMAL":
|
||||
case "DOUBLE":
|
||||
case "FLOAT":
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
} else if (value instanceof String) {
|
||||
return Double.parseDouble((String) value);
|
||||
}
|
||||
break;
|
||||
|
||||
case "VARCHAR":
|
||||
case "STRING":
|
||||
case "TEXT":
|
||||
return value.toString();
|
||||
|
||||
case "BOOLEAN":
|
||||
case "BOOL":
|
||||
if (value instanceof Boolean) {
|
||||
return value;
|
||||
} else if (value instanceof String) {
|
||||
return Boolean.parseBoolean((String) value);
|
||||
} else if (value instanceof Number) {
|
||||
return ((Number) value).intValue() != 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error converting value '{}' to type '{}': {}", value, dataType, e.getMessage());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
183
src/main/java/com/excelaccess/service/MigrationService.java
Normal file
183
src/main/java/com/excelaccess/service/MigrationService.java
Normal file
@@ -0,0 +1,183 @@
|
||||
package com.excelaccess.service;
|
||||
|
||||
import com.excelaccess.model.MappingConfig;
|
||||
import com.excelaccess.model.SheetMapping;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MigrationService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MigrationService.class);
|
||||
|
||||
private final ExcelService excelService;
|
||||
private final AccessService accessService;
|
||||
private final ConfigService configService;
|
||||
|
||||
public MigrationService() {
|
||||
this.excelService = new ExcelService();
|
||||
this.accessService = new AccessService();
|
||||
this.configService = new ConfigService();
|
||||
}
|
||||
|
||||
public void migrateData(String excelFilePath, String accessFilePath, Consumer<Double> progressCallback) throws Exception {
|
||||
logger.info("Starting migration from Excel: {} to Access: {}", excelFilePath, accessFilePath);
|
||||
|
||||
try {
|
||||
// Load configuration from home directory
|
||||
MappingConfig config = configService.loadConfigFromHomeDirectory();
|
||||
|
||||
// Update connection string with actual Access file path
|
||||
String connectionString = "jdbc:ucanaccess://" + accessFilePath;
|
||||
config.getDatabase().setConnectionString(connectionString);
|
||||
|
||||
// Connect to Access database
|
||||
accessService.connect(connectionString);
|
||||
|
||||
try {
|
||||
int totalMappings = config.getMappings().size();
|
||||
int completedMappings = 0;
|
||||
|
||||
for (SheetMapping mapping : config.getMappings()) {
|
||||
logger.info("Processing mapping: {} -> {}", mapping.getExcelSheet(), mapping.getAccessTable());
|
||||
|
||||
// Read data from Excel
|
||||
List<Map<String, Object>> data = excelService.readSheetData(excelFilePath, mapping);
|
||||
logger.info("Read {} rows from Excel sheet: {}", data.size(), mapping.getExcelSheet());
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
// Create table if needed
|
||||
if (config.getSettings().isCreateTableIfNotExists()) {
|
||||
accessService.createTableIfNotExists(mapping);
|
||||
}
|
||||
|
||||
// Clear existing data if needed (optional)
|
||||
// accessService.clearTable(mapping.getAccessTable());
|
||||
|
||||
// Insert data into Access
|
||||
accessService.insertData(mapping, data);
|
||||
logger.info("Successfully migrated {} rows to table: {}", data.size(), mapping.getAccessTable());
|
||||
} else {
|
||||
logger.warn("No data found in Excel sheet: {}", mapping.getExcelSheet());
|
||||
}
|
||||
|
||||
completedMappings++;
|
||||
double progress = (double) completedMappings / totalMappings;
|
||||
progressCallback.accept(progress);
|
||||
}
|
||||
|
||||
logger.info("Migration completed successfully");
|
||||
|
||||
} finally {
|
||||
// Disconnect from Access database
|
||||
accessService.disconnect();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Migration failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopMigration() {
|
||||
accessService.stopMigration();
|
||||
logger.info("Migration stop requested");
|
||||
}
|
||||
|
||||
public List<String> getExcelSheetNames(String excelFilePath) throws Exception {
|
||||
return excelService.getSheetNames(excelFilePath);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getExcelColumnHeaders(String excelFilePath, String sheetName) throws Exception {
|
||||
return excelService.getColumnHeaders(excelFilePath, sheetName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate data using ExcelAnalysisService and SqlGeneratorService
|
||||
*/
|
||||
public void migrateDataWithAnalysis(String excelFilePath, String accessFilePath, Consumer<Double> progressCallback) throws Exception {
|
||||
logger.info("Starting migration with analysis from Excel: {} to Access: {}", excelFilePath, accessFilePath);
|
||||
|
||||
try {
|
||||
// Load configuration from home directory
|
||||
MappingConfig config = configService.loadConfigFromHomeDirectory();
|
||||
|
||||
// Update connection string with actual Access file path
|
||||
String connectionString = "jdbc:ucanaccess://" + accessFilePath;
|
||||
config.getDatabase().setConnectionString(connectionString);
|
||||
|
||||
// Connect to Access database
|
||||
accessService.connect(connectionString);
|
||||
|
||||
try {
|
||||
// Analyze Excel file
|
||||
ExcelAnalysisService excelAnalysisService = new ExcelAnalysisService();
|
||||
ExcelAnalysisService.AnalysisResult analysisResult = excelAnalysisService.analyzeExcelFile(excelFilePath);
|
||||
|
||||
logger.info("Excel analysis completed. Found {} product cards and {} transactions",
|
||||
analysisResult.getProductCards().size(), analysisResult.getTransactions().size());
|
||||
|
||||
// Log to UI
|
||||
progressCallback.accept(0.2);
|
||||
|
||||
// Generate SQL statements
|
||||
SqlGeneratorService sqlGeneratorService = new SqlGeneratorService();
|
||||
sqlGeneratorService.setIdCurator(config.getSettings().getIdCurator());
|
||||
SqlGeneratorService.SqlGenerationResult sqlResult = sqlGeneratorService.generateSqlQueries(
|
||||
analysisResult.getProductCards(), analysisResult.getTransactions());
|
||||
|
||||
// Combine all SQL statements
|
||||
List<String> allSqlStatements = new ArrayList<>();
|
||||
allSqlStatements.addAll(sqlResult.getSkladInsertStatements());
|
||||
allSqlStatements.addAll(sqlResult.getTransactionsInsertStatements());
|
||||
|
||||
logger.info("Generated {} SQL statements", allSqlStatements.size());
|
||||
logger.info("Sklad statements: {}, Transaction statements: {}",
|
||||
sqlResult.getSkladInsertStatements().size(),
|
||||
sqlResult.getTransactionsInsertStatements().size());
|
||||
|
||||
// Log to UI
|
||||
progressCallback.accept(0.5);
|
||||
|
||||
// Execute SQL statements
|
||||
executeSqlStatements(allSqlStatements);
|
||||
|
||||
logger.info("Migration completed successfully");
|
||||
|
||||
// Update progress
|
||||
progressCallback.accept(1.0);
|
||||
|
||||
} finally {
|
||||
// Disconnect from Access database
|
||||
accessService.disconnect();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Migration failed", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeSqlStatements(List<String> sqlStatements) throws Exception {
|
||||
if (accessService.getConnection() == null || accessService.getConnection().isClosed()) {
|
||||
throw new Exception("Not connected to database");
|
||||
}
|
||||
|
||||
try (java.sql.Statement statement = accessService.getConnection().createStatement()) {
|
||||
for (String sql : sqlStatements) {
|
||||
if (sql.trim().isEmpty() || sql.trim().startsWith("--")) {
|
||||
continue; // Skip empty lines and comments
|
||||
}
|
||||
|
||||
logger.debug("Executing SQL: {}", sql);
|
||||
statement.execute(sql);
|
||||
}
|
||||
|
||||
logger.info("All SQL statements executed successfully");
|
||||
}
|
||||
}
|
||||
}
|
||||
174
src/main/java/com/excelaccess/service/SqlGeneratorService.java
Normal file
174
src/main/java/com/excelaccess/service/SqlGeneratorService.java
Normal file
@@ -0,0 +1,174 @@
|
||||
package com.excelaccess.service;
|
||||
|
||||
import com.excelaccess.service.ExcelAnalysisService.ProductCard;
|
||||
import com.excelaccess.service.ExcelAnalysisService.Transaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
public class SqlGeneratorService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SqlGeneratorService.class);
|
||||
|
||||
private int idCurator = 1; // Default value
|
||||
|
||||
public void setIdCurator(int idCurator) {
|
||||
this.idCurator = idCurator;
|
||||
}
|
||||
|
||||
public SqlGenerationResult generateSqlQueries(List<ProductCard> productCards, List<Transaction> transactions) {
|
||||
logger.info("Generating SQL queries for {} product cards and {} transactions",
|
||||
productCards.size(), transactions.size());
|
||||
|
||||
SqlGenerationResult result = new SqlGenerationResult();
|
||||
|
||||
// Generate INSERT statements for Склад table
|
||||
result.setSkladInsertStatements(generateSkladInsertStatements(productCards));
|
||||
|
||||
// Generate INSERT statements for Транзакции table
|
||||
result.setTransactionsInsertStatements(generateTransactionsInsertStatements(transactions, productCards));
|
||||
|
||||
logger.info("SQL generation completed successfully");
|
||||
logger.info("Generated {} Sklad INSERT statements", result.getSkladInsertStatements().size());
|
||||
logger.info("Generated {} Transactions INSERT statements", result.getTransactionsInsertStatements().size());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private List<String> generateSkladInsertStatements(List<ProductCard> productCards) {
|
||||
List<String> statements = new ArrayList<>();
|
||||
|
||||
statements.add("-- Вставка данных в таблицу Склад");
|
||||
|
||||
for (int i = 0; i < productCards.size(); i++) {
|
||||
ProductCard card = productCards.get(i);
|
||||
if (card.getName() == null || card.getName().trim().isEmpty()) {
|
||||
logger.debug("Skipping empty product card at index {}", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("Generating SQL for product card {}: {}", i, card.getName());
|
||||
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("INSERT INTO Склад ([Наименование], [Наименование у поставщика], [Проект], [Местоположение], [Категория], [Артикул], [Примечание]) VALUES (");
|
||||
sql.append(escapeString(truncateString(card.getName(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(card.getSupplierName(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(card.getProject(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(card.getLocation(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(card.getCategory(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(card.getArticleNumbers(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(card.getNote(), 255)));
|
||||
sql.append(");");
|
||||
|
||||
statements.add(sql.toString());
|
||||
logger.debug("Added SQL statement for product card: {}", card.getName());
|
||||
}
|
||||
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
private List<String> generateTransactionsInsertStatements(List<Transaction> transactions, List<ProductCard> productCards) {
|
||||
List<String> statements = new ArrayList<>();
|
||||
|
||||
statements.add("-- Вставка данных в таблицу Транзакции");
|
||||
statements.add("-- ID Компонента получается через поиск по нескольким полям");
|
||||
|
||||
for (int i = 0; i < transactions.size(); i++) {
|
||||
Transaction transaction = transactions.get(i);
|
||||
if (transaction.getName() == null || transaction.getName().trim().isEmpty()) {
|
||||
logger.debug("Skipping empty transaction at index {}", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("Generating SQL for transaction {}: {}", i, transaction.getName());
|
||||
|
||||
// Найдем соответствующий товар в Склад по нескольким полям
|
||||
String productCardName = transaction.getName();
|
||||
ProductCard matchingCard = findMatchingProductCard(productCardName, productCards);
|
||||
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("INSERT INTO Транзакции ([ID Компонента], [Приход], [Кто], [Дата], [Примечание], [№ накладной], [задача №], [Ответственный]) VALUES (");
|
||||
|
||||
if (matchingCard != null) {
|
||||
// Используем точное совпадение по нескольким полям
|
||||
sql.append("(SELECT TOP 1 [ID компонента] FROM Склад WHERE ");
|
||||
sql.append("[Наименование] = ").append(escapeString(truncateString(matchingCard.getName(), 255))).append(" AND ");
|
||||
sql.append("[Наименование у поставщика] = ").append(escapeString(truncateString(matchingCard.getSupplierName(), 255))).append(" AND ");
|
||||
sql.append("[Проект] = ").append(escapeString(truncateString(matchingCard.getProject(), 255))).append(" ");
|
||||
sql.append("ORDER BY [ID компонента] DESC), ");
|
||||
logger.debug("Found matching product card for transaction: {}", matchingCard.getName());
|
||||
} else {
|
||||
// ОШИБКА: товар не найден в карточках
|
||||
statements.add("-- ОШИБКА: Товар '" + productCardName + "' не найден в карточках товаров!");
|
||||
statements.add("-- Пропускаем эту транзакцию");
|
||||
logger.debug("No matching product card found for transaction: {}", productCardName);
|
||||
continue;
|
||||
}
|
||||
sql.append(transaction.getIncome() != null ? transaction.getIncome() : "0").append(", ");
|
||||
sql.append(escapeString(truncateString(transaction.getWho(), 255))).append(", ");
|
||||
sql.append(transaction.getDate() != null ? escapeDate(transaction.getDate()) : "NULL").append(", ");
|
||||
sql.append(escapeString(truncateString(transaction.getNote(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(transaction.getInvoiceNumber(), 255))).append(", ");
|
||||
sql.append(escapeString(truncateString(transaction.getTaskNumber(), 255))).append(", ");
|
||||
sql.append(idCurator);
|
||||
sql.append(");");
|
||||
|
||||
statements.add(sql.toString());
|
||||
logger.debug("Added SQL statement for transaction: {}", transaction.getName());
|
||||
}
|
||||
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
private ProductCard findMatchingProductCard(String transactionProductName, List<ProductCard> productCards) {
|
||||
// Ищем точное совпадение по наименованию
|
||||
String truncatedTransactionName = truncateString(transactionProductName, 255);
|
||||
for (ProductCard card : productCards) {
|
||||
if (card.getName() != null && truncateString(card.getName(), 255).equals(truncatedTransactionName)) {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String escapeString(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return "NULL";
|
||||
}
|
||||
return "'" + value.replace("'", "''") + "'";
|
||||
}
|
||||
|
||||
private String escapeDate(LocalDate date) {
|
||||
if (date == null) {
|
||||
return "NULL";
|
||||
}
|
||||
return "#" + date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + "#";
|
||||
}
|
||||
|
||||
private String truncateString(String str, int maxLength) {
|
||||
if (str == null) {
|
||||
return "";
|
||||
}
|
||||
if (str.length() <= maxLength) {
|
||||
return str;
|
||||
}
|
||||
logger.warn("Truncating string from {} to {} characters: {}", str.length(), maxLength, str);
|
||||
return str.substring(0, maxLength);
|
||||
}
|
||||
|
||||
public static class SqlGenerationResult {
|
||||
private List<String> skladInsertStatements;
|
||||
private List<String> transactionsInsertStatements;
|
||||
|
||||
// Getters and setters
|
||||
public List<String> getSkladInsertStatements() { return skladInsertStatements; }
|
||||
public void setSkladInsertStatements(List<String> skladInsertStatements) { this.skladInsertStatements = skladInsertStatements; }
|
||||
public List<String> getTransactionsInsertStatements() { return transactionsInsertStatements; }
|
||||
public void setTransactionsInsertStatements(List<String> transactionsInsertStatements) { this.transactionsInsertStatements = transactionsInsertStatements; }
|
||||
}
|
||||
}
|
||||
63
src/main/resources/css/styles.css
Normal file
63
src/main/resources/css/styles.css
Normal file
@@ -0,0 +1,63 @@
|
||||
.title-label {
|
||||
-fx-font-size: 20px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 15px;
|
||||
-fx-text-fill: #2c3e50;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 5px 0px;
|
||||
-fx-text-fill: #34495e;
|
||||
}
|
||||
|
||||
.button {
|
||||
-fx-background-color: #3498db;
|
||||
-fx-text-fill: white;
|
||||
-fx-font-weight: bold;
|
||||
-fx-padding: 10px 20px;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-background-radius: 5px;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
-fx-background-color: #2980b9;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
-fx-background-color: #bdc3c7;
|
||||
-fx-text-fill: #7f8c8d;
|
||||
}
|
||||
|
||||
.text-field {
|
||||
-fx-padding: 10px;
|
||||
-fx-border-color: #bdc3c7;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-background-radius: 5px;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
-fx-accent: #27ae60;
|
||||
-fx-background-color: #ecf0f1;
|
||||
}
|
||||
|
||||
.text-area {
|
||||
-fx-font-family: "Consolas", "Courier New", monospace;
|
||||
-fx-font-size: 11px;
|
||||
-fx-background-color: #f8f9fa;
|
||||
-fx-border-color: #dee2e6;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-background-radius: 5px;
|
||||
}
|
||||
|
||||
.log-area {
|
||||
-fx-font-family: "Consolas", "Courier New", monospace;
|
||||
-fx-font-size: 11px;
|
||||
-fx-background-color: #f8f9fa;
|
||||
-fx-border-color: #dee2e6;
|
||||
-fx-border-radius: 5px;
|
||||
-fx-background-radius: 5px;
|
||||
}
|
||||
49
src/main/resources/fxml/analysis-dialog.fxml
Normal file
49
src/main/resources/fxml/analysis-dialog.fxml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<Dialog xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.excelaccess.controller.AnalysisDialogController">
|
||||
<title>Анализ Excel файла</title>
|
||||
<headerText>Результаты анализа данных Excel</headerText>
|
||||
|
||||
<content>
|
||||
<VBox spacing="10.0" prefWidth="800.0" prefHeight="600.0">
|
||||
<children>
|
||||
<Label text="Карточки товаров:" styleClass="section-label" />
|
||||
<TableView fx:id="productCardsTable" prefHeight="200.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="nameColumn" text="Наименование" prefWidth="200.0" />
|
||||
<TableColumn fx:id="supplierColumn" text="Поставщик" prefWidth="150.0" />
|
||||
<TableColumn fx:id="projectColumn" text="Проект" prefWidth="150.0" />
|
||||
<TableColumn fx:id="categoryColumn" text="Категория" prefWidth="100.0" />
|
||||
<TableColumn fx:id="articleColumn" text="Артикул" prefWidth="100.0" />
|
||||
</columns>
|
||||
</TableView>
|
||||
|
||||
<Label text="Транзакции:" styleClass="section-label" />
|
||||
<TableView fx:id="transactionsTable" prefHeight="200.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="transactionNameColumn" text="Наименование" prefWidth="200.0" />
|
||||
<TableColumn fx:id="incomeColumn" text="Приход" prefWidth="80.0" />
|
||||
<TableColumn fx:id="whoColumn" text="Кто" prefWidth="150.0" />
|
||||
<TableColumn fx:id="dateColumn" text="Дата" prefWidth="100.0" />
|
||||
<TableColumn fx:id="invoiceColumn" text="Накладная" prefWidth="100.0" />
|
||||
<TableColumn fx:id="taskColumn" text="Задача" prefWidth="100.0" />
|
||||
</columns>
|
||||
</TableView>
|
||||
|
||||
<Label text="Статистика:" styleClass="section-label" />
|
||||
<TextArea fx:id="statisticsArea" editable="false" prefHeight="100.0" wrapText="true" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
</content>
|
||||
|
||||
<buttons>
|
||||
<Button text="Закрыть" onAction="#closeDialog" />
|
||||
</buttons>
|
||||
</Dialog>
|
||||
76
src/main/resources/fxml/main.fxml
Normal file
76
src/main/resources/fxml/main.fxml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.excelaccess.controller.MainController">
|
||||
<children>
|
||||
<Label text="Инструмент миграции Excel в Access" styleClass="title-label" />
|
||||
<Separator />
|
||||
|
||||
<VBox spacing="10.0">
|
||||
<children>
|
||||
<HBox spacing="10.0">
|
||||
<children>
|
||||
<Label text="Файл Excel:" minWidth="120.0" />
|
||||
<TextField fx:id="excelFilePath" editable="false" HBox.hgrow="ALWAYS" />
|
||||
<Button fx:id="browseButton" text="Обзор..." onAction="#browseExcelFile" />
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<HBox spacing="10.0">
|
||||
<children>
|
||||
<Label text="База данных Access:" minWidth="120.0" />
|
||||
<TextField fx:id="accessFilePath" editable="false" HBox.hgrow="ALWAYS" />
|
||||
<Button fx:id="browseAccessButton" text="Обзор..." onAction="#browseAccessFile" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
|
||||
<Separator />
|
||||
|
||||
<VBox spacing="10.0">
|
||||
<children>
|
||||
<Label text="Прогресс миграции:" />
|
||||
<ProgressBar fx:id="progressBar" prefWidth="500.0" />
|
||||
<Label fx:id="statusLabel" text="Готов к началу миграции" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
|
||||
<Separator />
|
||||
|
||||
<HBox spacing="10.0">
|
||||
<children>
|
||||
<Button fx:id="analyzeButton" text="Анализ Excel" onAction="#analyzeExcel" disable="true" />
|
||||
<Button fx:id="startButton" text="Начать миграцию" onAction="#startMigration" disable="true" />
|
||||
<Button fx:id="clearButton" text="Очистить" onAction="#clearFields" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
|
||||
<Separator />
|
||||
|
||||
<VBox spacing="5.0" VBox.vgrow="ALWAYS">
|
||||
<children>
|
||||
<Label text="Журнал операций:" />
|
||||
<TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS" wrapText="true" styleClass="log-area" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</VBox>
|
||||
3
src/main/resources/icons/app-icon.ico
Normal file
3
src/main/resources/icons/app-icon.ico
Normal file
@@ -0,0 +1,3 @@
|
||||
# Placeholder for app icon
|
||||
# You need to create a proper .ico file for Windows
|
||||
# You can use online converters or tools like GIMP to create this
|
||||
34
src/main/resources/logback.xml
Normal file
34
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>logs/migration-tool.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>logs/migration-tool.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<maxHistory>30</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.excelaccess" level="DEBUG" additivity="false">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</logger>
|
||||
|
||||
<logger name="net.ucanaccess" level="WARN" additivity="false">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</logger>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
</configuration>
|
||||
105
src/main/resources/mapping-config.json
Normal file
105
src/main/resources/mapping-config.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"database": {
|
||||
"connectionString": ""
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"excelSheet": "Карточки товара",
|
||||
"accessTable": "Склад",
|
||||
"columnMappings": [
|
||||
{
|
||||
"excelColumn": "A",
|
||||
"accessColumn": "Наименование",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "B",
|
||||
"accessColumn": "Наименование у поставщика",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "C",
|
||||
"accessColumn": "Проект",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "D",
|
||||
"accessColumn": "Местоположение",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "E",
|
||||
"accessColumn": "Категория",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "F",
|
||||
"accessColumn": "Артикул",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "G",
|
||||
"accessColumn": "Примечание",
|
||||
"dataType": "VARCHAR"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"excelSheet": "Транзакции",
|
||||
"accessTable": "Транзакции",
|
||||
"columnMappings": [
|
||||
{
|
||||
"excelColumn": "A",
|
||||
"accessColumn": "Наименование",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "B",
|
||||
"accessColumn": "Приход",
|
||||
"dataType": "INTEGER"
|
||||
},
|
||||
{
|
||||
"excelColumn": "C",
|
||||
"accessColumn": "Кто",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "D",
|
||||
"accessColumn": "Дата",
|
||||
"dataType": "DATE"
|
||||
},
|
||||
{
|
||||
"excelColumn": "E",
|
||||
"accessColumn": "Примечание",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "F",
|
||||
"accessColumn": "№ накладной",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "G",
|
||||
"accessColumn": "задача №",
|
||||
"dataType": "VARCHAR"
|
||||
},
|
||||
{
|
||||
"excelColumn": "H",
|
||||
"accessColumn": "Ответственный",
|
||||
"dataType": "VARCHAR"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"skipEmptyRows": true,
|
||||
"skipHeaderRow": true,
|
||||
"batchSize": 1000,
|
||||
"createTableIfNotExists": false,
|
||||
"id_curator": 1
|
||||
},
|
||||
"dsol_factory": {
|
||||
"base_url": "",
|
||||
"api_get_label_id": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user