-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
602 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,6 @@ go.work.sum | |
|
||
# env file | ||
.env | ||
|
||
local/ | ||
bin/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,57 @@ | ||
# thwInventurMerge | ||
Dieses kleine Toole ermöglicht es erfasstes Equipment mir den THW Inventurdaten aus THWin zu mergen. | ||
# thwInventoryMerge | ||
Dieses kleine Tool ermöglicht es, erfasstes Equipment mit den THW-Inventurdaten aus THWin zu mergen. Das Equipment wird dabei mittels Barcode-Scannern erfasst, die die gescannten Codes als CSV-Dateien speichern. | ||
|
||
## Installation | ||
Das Tool lädt man am einfachsten aus der [Releases-Sektion](https://github.com/mvach/thwInventoryMerge/releases) herunter und legt es in ein beliebiges leeres Verzeichnis (das `working_dir`). | ||
|
||
## Konfiguration | ||
Die CSV-Dateien mit den erfassten Barcodes der Scanner sowie die (aus THWin exportierte) Inventur-Excel-Datei legt man am besten ebenfalls in das `working_dir`. | ||
|
||
Zudem erstellt man eine Konfigurationsdatei (`config.json`), die man am einfachsten auch in das `working_dir` legt. | ||
|
||
## Hier ist eine Beispielkonfiguration: | ||
|
||
``` | ||
// config.json | ||
{ | ||
"excel_file_name": "20240101_Bestand.xlsx", | ||
"excel_config": { | ||
"worksheet_name": "N", | ||
"equipment_id_column_name": "Inventar Nr", | ||
"equipment_available_column_name": "Verfügbar" | ||
} | ||
} | ||
``` | ||
|
||
### Verzeichnisstruktur | ||
|
||
``` | ||
C:/ | ||
└── Users/ | ||
└── DeinUser/ | ||
└── MeinArbeitsverzeichnis/ | ||
├── config.json | ||
├── 20240101_Bestand.xlsx | ||
├── scanner1.csv | ||
├── scanner2.csv | ||
├── scanner3.csv | ||
└── thwInventoryMerge.exe | ||
``` | ||
|
||
### CSV Beispiel | ||
```csv | ||
// scanner1.csv | ||
0001-S001304 | ||
0509-002494 | ||
0509-002494 | ||
0591-S002360 | ||
0591-002781 | ||
0591-002781 | ||
0591-S002318 | ||
... | ||
``` | ||
|
||
## Ausführung | ||
|
||
Liegen alle Dateien gemeinsam im `working_dir`, kann thwInventoryMerge.exe einfach per Doppelklick ausgeführt werden. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package app | ||
|
||
import ( | ||
"encoding/csv" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"thwInventoryMerge/config" | ||
|
||
"github.com/xuri/excelize/v2" | ||
) | ||
|
||
type UpdateExcel interface { | ||
Update() error | ||
} | ||
|
||
type updateExcel struct { | ||
config config.Config | ||
} | ||
|
||
func NewUpdateExcel(config config.Config) UpdateExcel { | ||
return &updateExcel{ | ||
config: config, | ||
} | ||
} | ||
|
||
func (u *updateExcel) Update() error { | ||
recordedInventory, err := u.getRecordedInventory() | ||
if err != nil { | ||
return fmt.Errorf("failed to get recorded inventory: %w", err) | ||
} | ||
|
||
excelFile, err := excelize.OpenFile(u.config.GetAbsoluteExcelFileName()) | ||
if err != nil { | ||
return fmt.Errorf("failed to open the Excel file: %w", err) | ||
} | ||
|
||
rows, err := excelFile.GetRows(u.config.ExcelConfig.WorksheetName) | ||
if err != nil { | ||
return fmt.Errorf("failed to get rows from Excel file: %w", err) | ||
} | ||
|
||
for rowIndex, row := range rows { | ||
if rowIndex == 0 { | ||
err := u.getHeaderIndices(row) | ||
if err != nil { | ||
return fmt.Errorf("failed to get headers from Excel file: %w", err) | ||
} | ||
continue | ||
} | ||
|
||
// Check if the equipment ID exists in the inventory numbers | ||
equipmentID := row[*u.config.ExcelConfig.EquipmentIDColumnIndex] | ||
if count, exists := recordedInventory[strings.ToLower(equipmentID)]; exists { | ||
|
||
// Set the value in the specified cell | ||
cell, err := excelize.CoordinatesToCellName(*u.config.ExcelConfig.EquipmentAvailableColumnIndex+1, rowIndex+1) | ||
if err != nil { | ||
return fmt.Errorf("failed to get cell from coordinates: %w", err) | ||
} | ||
excelFile.SetCellValue( | ||
u.config.ExcelConfig.WorksheetName, | ||
cell, | ||
count, | ||
) | ||
} | ||
} | ||
|
||
if err := excelFile.Save(); err != nil { | ||
return fmt.Errorf("failed to save the updated Excel file: %w", err) | ||
} else { | ||
fmt.Println("\nUpdated Excel file successfully.") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (u *updateExcel) getHeaderIndices(row []string) error { | ||
for j, col := range row { | ||
if strings.EqualFold(col, u.config.ExcelConfig.EquipmentIDColumnName) { | ||
u.config.ExcelConfig.EquipmentIDColumnIndex = &j | ||
} else if strings.EqualFold(col, u.config.ExcelConfig.EquipmentAvailableColumnName) { | ||
u.config.ExcelConfig.EquipmentAvailableColumnIndex = &j | ||
} | ||
} | ||
|
||
if u.config.ExcelConfig.EquipmentIDColumnIndex == nil { | ||
return fmt.Errorf( | ||
"failed to find header %s in first row of worksheet %s", | ||
u.config.ExcelConfig.EquipmentIDColumnName, | ||
u.config.ExcelConfig.WorksheetName, | ||
) | ||
} | ||
if u.config.ExcelConfig.EquipmentAvailableColumnIndex == nil { | ||
return fmt.Errorf( | ||
"failed to find header %s in first row of worksheet %s", | ||
u.config.ExcelConfig.EquipmentAvailableColumnName, | ||
u.config.ExcelConfig.WorksheetName, | ||
) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (u *updateExcel) getRecordedInventory() (map[string]int, error) { | ||
inventoryNumbers := make(map[string]int) | ||
|
||
csvFiles, err := u.config.GetCSVFiles() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get CSV files: %w", err) | ||
} | ||
|
||
for _, file := range csvFiles { | ||
f, err := os.Open(file) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to open CSV file: %w", err) | ||
} | ||
defer f.Close() | ||
|
||
reader := csv.NewReader(f) | ||
records, err := reader.ReadAll() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read CSV file: %w", err) | ||
} | ||
|
||
for _, record := range records { | ||
if len(record) > 0 { | ||
inventoryNumbers[strings.ToLower(record[0])]++ | ||
} | ||
} | ||
} | ||
|
||
u.printInventoryNumbers(inventoryNumbers) | ||
|
||
return inventoryNumbers, nil | ||
} | ||
|
||
func (u *updateExcel) printInventoryNumbers(inventoryNumbers map[string]int) { | ||
fmt.Printf("%-20s %s\n", "Inventory Number", "Quantity") | ||
fmt.Println(strings.Repeat("-", 30)) // Print a separator line | ||
|
||
for number, quantity := range inventoryNumbers { | ||
fmt.Printf("%-20s %d\n", number, quantity) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package config | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
type Config struct { | ||
WorkingDir string `json:"working_dir"` | ||
ExcelFileName string `json:"excel_file_name"` | ||
ExcelConfig struct { | ||
WorksheetName string `json:"worksheet_name"` | ||
EquipmentIDColumnName string `json:"equipment_id_column_name"` | ||
EquipmentIDColumnIndex *int | ||
EquipmentAvailableColumnName string `json:"equipment_available_column_name"` | ||
EquipmentAvailableColumnIndex *int | ||
} `json:"excel_config"` | ||
} | ||
|
||
func (c *Config) GetCSVFiles() ([]string, error) { | ||
var csvFiles []string | ||
|
||
files, err := os.ReadDir(c.WorkingDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, file := range files { | ||
if !file.IsDir() && filepath.Ext(file.Name()) == ".csv" { | ||
csvFiles = append(csvFiles, filepath.Join(c.WorkingDir, file.Name())) | ||
} | ||
} | ||
|
||
return csvFiles, nil | ||
} | ||
|
||
func (c *Config) GetAbsoluteExcelFileName() string { | ||
return filepath.Join(c.WorkingDir, c.ExcelFileName) | ||
} | ||
|
||
func LoadConfig(filePath string) (*Config, error) { | ||
data, err := os.ReadFile(filePath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var config Config | ||
|
||
err = json.Unmarshal(data, &config) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to load invalid config file, %w", err) | ||
} | ||
|
||
err = config.validate() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to validate the config file, %w", err) | ||
} | ||
|
||
return &config, nil | ||
} | ||
|
||
func (c Config) validate() error { | ||
if c.ExcelFileName == "" { | ||
return errors.New("property excel_file_name is required") | ||
} | ||
if c.ExcelConfig.WorksheetName == "" { | ||
return errors.New("property excel_config.worksheet_name is required") | ||
} | ||
if c.ExcelConfig.EquipmentIDColumnName == "" { | ||
return errors.New("property excel_config.equipment_id_column_name is required") | ||
} | ||
if c.ExcelConfig.EquipmentAvailableColumnName == "" { | ||
return errors.New("property excel_config.equipment_available_column_name is required") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package config_test | ||
|
||
import ( | ||
"testing" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
func TestConfig(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "Config Suite") | ||
} |
Oops, something went wrong.