Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cf0792866 | |||
| 719e227ba4 | |||
| ace45b1cc9 | |||
| 0bd47658e9 | |||
| 97a592b0c9 | |||
| 4bbbb0334c | |||
| 27d5fb26d9 | |||
| 5119d4bb31 | |||
| acaf537f11 | |||
| 55ff314163 | |||
| 376b8c54a6 | |||
| 1a048a0e07 | |||
| 77fc5920a2 | |||
| 78d48db9ea | |||
| 92e3a3f0f9 | |||
| 57cb27f6cf | |||
| c24dae9694 | |||
| e0ac23a8e0 | |||
| c1a06dc44c | |||
| 72f6e0c822 | |||
| b2418d8c7e | |||
| 9cb057668b | |||
| cb2aa89b16 | |||
| ef9a9296dd | |||
| 3c4b2e148d | |||
| bf1bc84cd0 | |||
| 8e37f9e776 | |||
| 216abc8ad2 | |||
| cffa3d789b | |||
| d7ef1573e5 | |||
| e101833c7d | |||
| 6c98919c80 | |||
| cae35a266f | |||
| c57e8ab6db | |||
| 135b14adcf | |||
| 9f59e122ef | |||
| 43cc2a3caa | |||
|
|
2f2998b0fd | ||
| 788b67804e | |||
| 2bb38570f9 | |||
| aacbce2557 | |||
| 6c5085093d | |||
| 423d563cc0 |
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
pos_database.db
|
pos_database.db
|
||||||
ScannerGO/ScannerGO-*
|
ScannerGO/ScannerGO-*
|
||||||
ScannerGO/config.json
|
ScannerGO/config.json
|
||||||
|
DataToolsGO/imageTools-*
|
||||||
|
.env
|
||||||
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Build SekiPOS (F5)",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build-sekipos"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"python-envs.defaultEnvManager": "ms-python.python:pyenv"
|
||||||
|
}
|
||||||
15
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build-sekipos",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker build -t sekipos:latest .",
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "new"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
42
DataToolsGO/build.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Define binary names
|
||||||
|
LINUX_BIN="imageTools-linux"
|
||||||
|
LINUX_ARM_BIN="imageTools-linuxARMv7"
|
||||||
|
WINDOWS_BIN="imageTools-windows.exe"
|
||||||
|
|
||||||
|
echo "Starting build process..."
|
||||||
|
|
||||||
|
# Build for Linux (64-bit)
|
||||||
|
echo "Building for Linux..."
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o "$LINUX_BIN" main.go
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully built: $LINUX_BIN"
|
||||||
|
else
|
||||||
|
echo "Failed to build Linux binary"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build for Windows (64-bit)
|
||||||
|
echo "Building for Windows..."
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o "$WINDOWS_BIN" main.go
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully built: $WINDOWS_BIN"
|
||||||
|
else
|
||||||
|
echo "Failed to build Windows binary"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build for Linux ARM (ARMv7)
|
||||||
|
echo "Building for Linux ARMv7..."
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -o "$LINUX_ARM_BIN" main.go
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully built: $LINUX_ARM_BIN"
|
||||||
|
else
|
||||||
|
echo "Failed to build Linux ARMv7 binary"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build complete."
|
||||||
5
DataToolsGO/go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module dataTools
|
||||||
|
|
||||||
|
go 1.25.7
|
||||||
|
|
||||||
|
require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
2
DataToolsGO/go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
84
DataToolsGO/main.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Command line arguments
|
||||||
|
dirPath := flag.String("dir", "./", "Directory containing images")
|
||||||
|
maxWidth := flag.Uint("width", 1000, "Maximum width for resizing")
|
||||||
|
quality := flag.Int("quality", 75, "JPEG quality (1-100)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
files, err := os.ReadDir(*dirPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error reading directory: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Processing images in %s (Max Width: %d, Quality: %d)\n", *dirPath, *maxWidth, *quality)
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := strings.ToLower(filepath.Ext(file.Name()))
|
||||||
|
if ext != ".jpg" && ext != ".jpeg" && ext != ".png" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(*dirPath, file.Name())
|
||||||
|
processImage(filePath, *maxWidth, *quality)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Done. Your storage can finally breathe again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func processImage(path string, maxWidth uint, quality int) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to open %s: %v\n", path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
img, _, err := image.Decode(file)
|
||||||
|
file.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to decode %s: %v\n", path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only resize if original is wider than maxWidth
|
||||||
|
bounds := img.Bounds()
|
||||||
|
var finalImg image.Image
|
||||||
|
if uint(bounds.Dx()) > maxWidth {
|
||||||
|
finalImg = resize.Resize(maxWidth, 0, img, resize.Lanczos3)
|
||||||
|
fmt.Printf("Resized and compressed: %s\n", filepath.Base(path))
|
||||||
|
} else {
|
||||||
|
finalImg = img
|
||||||
|
fmt.Printf("Compressed (no resize needed): %s\n", filepath.Base(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite the original file
|
||||||
|
out, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to create output file %s: %v\n", path, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
err = jpeg.Encode(out, finalImg, &jpeg.Options{Quality: quality})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to encode %s: %v\n", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
COPY app.py .
|
COPY app.py .
|
||||||
COPY templates/ ./templates/
|
COPY templates/ ./templates/
|
||||||
COPY static/ ./static/
|
COPY static/ ./static/
|
||||||
|
#COPY .env .
|
||||||
|
|
||||||
# Create the folder structure for the volume mounts
|
# Create the folder structure for the volume mounts
|
||||||
RUN mkdir -p /app/static/cache
|
RUN mkdir -p /app/static/cache
|
||||||
|
|||||||
162
KeyGenerator/frutas2.json
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Paltas",
|
||||||
|
"plu": "3509",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3509-gem-avocado_1625011346.JPG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Papas",
|
||||||
|
"plu": "3414",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3414-baking-potato-white_1635179333.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Limones",
|
||||||
|
"plu": "3626",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3626-meyer-lemons_1460404763.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cebollines / Cebollas",
|
||||||
|
"plu": "4068",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4068-onions-green-2_1625063600.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ajo",
|
||||||
|
"plu": "4608",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zanahorias",
|
||||||
|
"plu": "4560",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Plátanos",
|
||||||
|
"plu": "4235",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4235-plantains-01_1625076376.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Manzanas",
|
||||||
|
"plu": "4099",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/apples-akane_1629314651.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Naranjas",
|
||||||
|
"plu": "4381",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4381-oranges-blood-01_1625082045.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Choclo",
|
||||||
|
"plu": "3087",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zapallo",
|
||||||
|
"plu": "4734",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4734-mini-pumpkin_1633964765.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zapallo italiano",
|
||||||
|
"plu": "4750",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4750-squash-acorn-01_1625751871.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Repollo",
|
||||||
|
"plu": "4069",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Betarragas",
|
||||||
|
"plu": "4537",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Porotos verdes",
|
||||||
|
"plu": "4527",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4527-beans-chinese-long-07_1625671743.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mandarinas",
|
||||||
|
"plu": "3524",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/noble-juicycrunch-productisolated-900x900-rgb-copy_1627662136.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Uvas",
|
||||||
|
"plu": "3491",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/arra-15-grape_1518122851.JPG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Duraznos",
|
||||||
|
"plu": "3113",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3113-peaches-donut-04_1614707155.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cerezas",
|
||||||
|
"plu": "3549",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/screenshot-2023-05-02-100428_1683036305.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peras",
|
||||||
|
"plu": "3317",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3317-angelys-pear_1460402454.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ciruelas",
|
||||||
|
"plu": "4435",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01_1625667846.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kiwi",
|
||||||
|
"plu": "3279",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3279-kiwi-gold-03_1614718637.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Piña",
|
||||||
|
"plu": "4430",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4430-pineapple-05_1625667649.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Puerros",
|
||||||
|
"plu": "4629",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Alcachofas",
|
||||||
|
"plu": "4519",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4519-artichokes-baby-02_1625671366.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Espárragos",
|
||||||
|
"plu": "4521",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01_1625671446.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Berenjena",
|
||||||
|
"plu": "4599",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Durazno Nectarin",
|
||||||
|
"plu": "3437",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/yellow-fleshed-flat-nectarine_1629140965.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Damascos",
|
||||||
|
"plu": "3044",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3044-apricots-black-velvet-03_1614619576.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Membrillo",
|
||||||
|
"plu": "4447",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Champiñones",
|
||||||
|
"plu": "4647",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4647-mushrooms-chanterelles-1_1625681503.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Castañas",
|
||||||
|
"plu": "4927",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -11,7 +11,7 @@ from io import BytesIO
|
|||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
JSON_FILE = os.path.join(os.getcwd(), 'frutas.json')
|
JSON_FILE = os.path.join(os.getcwd(), 'frutas2.json')
|
||||||
OUTPUT_DIR = os.path.join(os.getcwd(), 'keychain_cards')
|
OUTPUT_DIR = os.path.join(os.getcwd(), 'keychain_cards')
|
||||||
IMG_CACHE_DIR = os.path.join(os.getcwd(), 'image_cache')
|
IMG_CACHE_DIR = os.path.join(os.getcwd(), 'image_cache')
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 63 KiB |
@@ -1,437 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"PLU": "4924",
|
|
||||||
"Name": "Almonds",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4924-almonds_1630598936.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3525",
|
|
||||||
"Name": "Apples",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3486-cn121-apple-image_1627662287.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4218",
|
|
||||||
"Name": "Apricots",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3302-apricots-1-1614718940_1633443369.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4516",
|
|
||||||
"Name": "Artichokes",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4516-artichokes_1630598556.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4080",
|
|
||||||
"Name": "Asparagus",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01-1625671446_1632939137.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4046",
|
|
||||||
"Name": "Avocados",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4770-hass-avocado_1635960530.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3303",
|
|
||||||
"Name": "Babaco",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3303-babacoa_1614719281.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4186",
|
|
||||||
"Name": "Bananas",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4011-yellow-banana_1634142809.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4528",
|
|
||||||
"Name": "Beans",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4528-beans-fava-07_1625671831.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4537",
|
|
||||||
"Name": "Beets",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4247",
|
|
||||||
"Name": "Berries",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4028-berries-strawberries-06-1625013614_1633444773.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4926",
|
|
||||||
"Name": "Brazilnuts",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4926-brazil-nuts-02_1625756785.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4069",
|
|
||||||
"Name": "Cabbage",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4560",
|
|
||||||
"Name": "Carrots",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3105",
|
|
||||||
"Name": "Cashews",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3105-cashews-03_1614635878.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3321",
|
|
||||||
"Name": "Celery Root/Celeriac",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3321-celery-root-03_1614720213.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3357",
|
|
||||||
"Name": "Cherries",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4045-cherries-05-1625061853_1633445797.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4927",
|
|
||||||
"Name": "Chestnuts",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4261",
|
|
||||||
"Name": "Coconuts",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4261-coconuts-02_1625077777.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3087",
|
|
||||||
"Name": "Corn",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4862",
|
|
||||||
"Name": "Dates",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4862-dates_1630598790.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4892",
|
|
||||||
"Name": "Dill",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4892-dill-baby-02_1625755376.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4599",
|
|
||||||
"Name": "Eggplant (Aubergine)",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4606",
|
|
||||||
"Name": "Fiddlehead Ferns",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4606-fiddlehead-ferns-07_1625679559.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4266",
|
|
||||||
"Name": "Figs",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4266-figs-03_1625077969.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4608",
|
|
||||||
"Name": "Garlic",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4612",
|
|
||||||
"Name": "Ginger Root",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4612-ginger-root-06_1625679690.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3091",
|
|
||||||
"Name": "Gobo Root/Burdock",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3091gobo-root-02_1614634374.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4280",
|
|
||||||
"Name": "Grapefruit",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4492-grapefruit-ruby-red_1633960726.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3505",
|
|
||||||
"Name": "Grapes",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/arra-32-image-3_1629296403.PNG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4614",
|
|
||||||
"Name": "Greens",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4614-collard-greens-trimmed_1625679839.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4625",
|
|
||||||
"Name": "Horseradish Root",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4625-horseradish-root-04_1625680026.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4626",
|
|
||||||
"Name": "Jicama/Yam Bean",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4626-jicama_1635179919.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3280",
|
|
||||||
"Name": "Kiwifruit",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4030-kiwi-fruit-04-1625013864_1633447661.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4303",
|
|
||||||
"Name": "Kumquat",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4303-kumquats-03_1625079229.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4629",
|
|
||||||
"Name": "Leeks",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4033",
|
|
||||||
"Name": "Lemons",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4033-lemons_1630597866.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4328",
|
|
||||||
"Name": "Limequats",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4328-limequats-01_1625081231.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4048",
|
|
||||||
"Name": "Limes",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4048-limes-02_1625062144.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4308",
|
|
||||||
"Name": "Loquats",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4308-lychees-5_1625079574.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3099",
|
|
||||||
"Name": "Lotus Root",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3099-lotus-root-03_1614635240.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3106",
|
|
||||||
"Name": "Macadamia",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3106-macadamias_1614705972.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4644",
|
|
||||||
"Name": "Malanga",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4644-malanga_1633956612.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3475",
|
|
||||||
"Name": "Mint",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/peppermint-3475_1625790587.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4645",
|
|
||||||
"Name": "Mushrooms",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4645-mushrooms-white-button-02_1625681263.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3276",
|
|
||||||
"Name": "Name",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3276-name-white_1634146226.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4377",
|
|
||||||
"Name": "Nectarine",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4378-nectarines-1-1625081931_1633448513.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4665",
|
|
||||||
"Name": "Onions",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4093-onions-yellow-01-1625064621_1632941716.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4013",
|
|
||||||
"Name": "Oranges",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4012-navel-orange_1634141444.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4672",
|
|
||||||
"Name": "Parsnip",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4672-parsnip_1633958885.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3311",
|
|
||||||
"Name": "Passion Fruit",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3311-passion-fruit-02_1614719436.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4037",
|
|
||||||
"Name": "Peaches",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4403-peaches-1-1625664942_1633449692.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4931",
|
|
||||||
"Name": "Peanuts",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4931-peanuts_1625757234.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4025",
|
|
||||||
"Name": "Pears",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4025-anjou-pear_1629988897.PNG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4673",
|
|
||||||
"Name": "Peas",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4673-peas-black-eyed-01_1625684339.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4936",
|
|
||||||
"Name": "Pecans",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4936-pecans-01_1625757366.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3459",
|
|
||||||
"Name": "Persimmon",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3459-shiny-red-persimmon_1486570958.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3039",
|
|
||||||
"Name": "Physalis/Cape Gooseberry/Ground Cherry",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3039-cape-gooseberry_1630596655.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4029",
|
|
||||||
"Name": "Pineapple",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4029-pineapple_1630597815.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4939",
|
|
||||||
"Name": "Pistachio",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4939-pistachios_1625757519.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3040",
|
|
||||||
"Name": "Pitahaya",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3040-pitaya-dragon-fruit-01_1614282658.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3611",
|
|
||||||
"Name": "Plumcot (Interspecific Plum)",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/interspecific-plum-black-picture_1629142350.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4434",
|
|
||||||
"Name": "Plums",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01-1625667846_1633453839.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4445",
|
|
||||||
"Name": "Pomegranate",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4445-pomegranates-07_1625668628.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3128",
|
|
||||||
"Name": "Potato",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3128-potatoes-purple-06_1614708225.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3631",
|
|
||||||
"Name": "Pumpkin",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3631-pink-pumpkin_1460472104.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3480",
|
|
||||||
"Name": "Pumpkin Vine",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/pumpkin-vine-3480_1625790222.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3478",
|
|
||||||
"Name": "Quelites",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/quelite-3478_1625790359.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4447",
|
|
||||||
"Name": "Quince",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3168",
|
|
||||||
"Name": "Radicchio",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3168-radicchio-castelfranco-02_1614718366.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4739",
|
|
||||||
"Name": "Radish",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4739-radish-black-03_1625751544.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4745",
|
|
||||||
"Name": "Rhubarb",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4745-rhubarb-01_1625751764.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4747",
|
|
||||||
"Name": "Rutabagas (Swede)",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4747-rutabaga_1635168852.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3137",
|
|
||||||
"Name": "Sapote",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/3137-sapote-white_1635264028.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4757",
|
|
||||||
"Name": "Squash",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4757-squash-banana_1625752222.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4790",
|
|
||||||
"Name": "Sugar Cane",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4790-sugar-cane_1630598686.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4791",
|
|
||||||
"Name": "Sunchokes (Jerusalem Artichokes)",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4791-sunchokes-03_1625753005.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4942",
|
|
||||||
"Name": "Sunflower Seeds",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4942-sunflower-seeds_1630599026.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4074",
|
|
||||||
"Name": "Sweet Potato/Yam/Kumara",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/sweet-potato-red-flesh-4074_1614281686.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4448",
|
|
||||||
"Name": "Tamarind",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4448-tamarindo-01_1625669100.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3428",
|
|
||||||
"Name": "Tangerines/Mandarins",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/murcott-mandarin-1629310772_1633456088.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4794",
|
|
||||||
"Name": "Taro Root (Dasheen)",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4795-taro-root-01-1625753179_1633374616.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4811",
|
|
||||||
"Name": "Turnip",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4811-turnip-purple-top_1635177577.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4943",
|
|
||||||
"Name": "Walnuts",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4943-walnuts_1625757652.JPG"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4814",
|
|
||||||
"Name": "Water Chestnuts",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4814-waterchestnuts-01_1625753939.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "3481",
|
|
||||||
"Name": "Xpelon",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/expelon-3481_1625790150.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"PLU": "4819",
|
|
||||||
"Name": "Yuca Root/Cassava/Manioc",
|
|
||||||
"URL": "https://server-ifps.accurateig.com/assets/commodities/4819-yucca-root-03_1625754507.jpg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
8
MPStudyGO/go.mod
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module MPPOS
|
||||||
|
|
||||||
|
go 1.25.7
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
)
|
||||||
4
MPStudyGO/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
143
MPStudyGO/main.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request structures based on MP documentation
|
||||||
|
type PrintRequest struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ExternalReference string `json:"external_reference"`
|
||||||
|
Config PrintConfig `json:"config"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrintConfig struct {
|
||||||
|
Point PointSettings `json:"point"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointSettings struct {
|
||||||
|
TerminalID string `json:"terminal_id"`
|
||||||
|
Subtype string `json:"subtype"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := godotenv.Load("../.env")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error loading .env file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example receipt using supported tags: {b}, {center}, {br}, {s}
|
||||||
|
receiptContent := "{center}{b}SEKIPOS VENTA{/b}{br}" +
|
||||||
|
"--------------------------------{br}" +
|
||||||
|
"{left}Producto: Choripan Premium{br}" +
|
||||||
|
"{left}Total: $5.500{br}" +
|
||||||
|
"--------------------------------{br}" +
|
||||||
|
"{center}{s}Gracias por su compra{/s}{br}"
|
||||||
|
|
||||||
|
resp, err := SendPrintAction(receiptContent)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Response: %s\n", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendPrintAction(content string) (string, error) {
|
||||||
|
apiURL := "https://api.mercadopago.com/terminals/v1/actions"
|
||||||
|
accessToken := os.Getenv("MP_ACCESS_TOKEN")
|
||||||
|
terminalID := os.Getenv("MP_TERMINAL_ID")
|
||||||
|
|
||||||
|
payload := PrintRequest{
|
||||||
|
Type: "print", // Required
|
||||||
|
ExternalReference: fmt.Sprintf("ref_%d", time.Now().Unix()),
|
||||||
|
Config: PrintConfig{
|
||||||
|
Point: PointSettings{
|
||||||
|
TerminalID: terminalID,
|
||||||
|
Subtype: "custom", // For text with tags
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Content: content, // Must be 100-4096 chars
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(payload)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
// Mandatory Unique UUID V4
|
||||||
|
req.Header.Set("X-Idempotency-Key", uuid.New().String())
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("MP Error %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PartialRefund(orderID string, paymentID string, amount string) (string, error) {
|
||||||
|
// Endpoint para reembolsos según la referencia de API
|
||||||
|
apiURL := fmt.Sprintf("https://api.mercadopago.com/v1/orders/%s/refund", orderID)
|
||||||
|
token := os.Getenv("MP_ACCESS_TOKEN")
|
||||||
|
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"transactions": []map[string]string{
|
||||||
|
{
|
||||||
|
"id": paymentID,
|
||||||
|
"amount": amount, // Debe ser un string sin decimales
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(body))
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-Idempotency-Key", uuid.New().String())
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
resBody, _ := io.ReadAll(resp.Body)
|
||||||
|
return string(resBody), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOrderStatus(orderID string) (string, error) {
|
||||||
|
apiURL := fmt.Sprintf("https://api.mercadopago.com/v1/orders/%s", orderID)
|
||||||
|
token := os.Getenv("MP_ACCESS_TOKEN")
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", apiURL, nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
resBody, _ := io.ReadAll(resp.Body)
|
||||||
|
return string(resBody), nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# SekiPOS v1.6 🍫🥤
|
# SekiPOS v2.0 🍫🥤
|
||||||
|
|
||||||
A reactive POS inventory system for software engineers with a snack addiction. Features real-time UI updates, automatic product discovery via Open Food Facts, and local image caching.
|
A reactive POS inventory system for software engineers with a snack addiction. Features real-time UI updates, automatic product discovery via Open Food Facts, and local image caching.
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ python app.py
|
|||||||
|
|
||||||
## 📋 TODOs?
|
## 📋 TODOs?
|
||||||
- Some form of user registration(?)
|
- Some form of user registration(?)
|
||||||
|
- Major refactoring of the codebase
|
||||||
|
|
||||||
## 🥼 Food Datasets
|
## 🥼 Food Datasets
|
||||||
- https://www.ifpsglobal.com/plu-codes-search
|
- https://www.ifpsglobal.com/plu-codes-search
|
||||||
|
|||||||
5
ScaleHardware/IOT_Scale/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
10
ScaleHardware/IOT_Scale/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
37
ScaleHardware/IOT_Scale/include/README
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
46
ScaleHardware/IOT_Scale/lib/README
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
16
ScaleHardware/IOT_Scale/platformio.ini
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:waveshare_rp2040_zero]
|
||||||
|
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||||
|
board = waveshare_rp2040_zero
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_archive = no
|
||||||
89
ScaleHardware/IOT_Scale/src/main.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
const int SCLK_PIN = 18;
|
||||||
|
const int DOUT_PIN = 19;
|
||||||
|
const int TARE_BUTTON_PIN = 4;
|
||||||
|
|
||||||
|
long offset = 0;
|
||||||
|
float scaleFactor = 1.0; // Update this after your calibration test
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
pinMode(SCLK_PIN, OUTPUT);
|
||||||
|
pinMode(DOUT_PIN, INPUT);
|
||||||
|
pinMode(TARE_BUTTON_PIN, INPUT_PULLUP);
|
||||||
|
|
||||||
|
digitalWrite(SCLK_PIN, LOW);
|
||||||
|
|
||||||
|
Serial.println("Initializing SD10819 ADC...");
|
||||||
|
delay(500); // Wait for chip stabilization
|
||||||
|
calibrateZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
long readADC() {
|
||||||
|
// 1. Wait for DRDY to go LOW
|
||||||
|
// Timeout added to prevent infinite loop if ADC is disconnected
|
||||||
|
unsigned long startWait = millis();
|
||||||
|
while (digitalRead(DOUT_PIN) == HIGH) {
|
||||||
|
if (millis() - startWait > 500) return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long value = 0;
|
||||||
|
|
||||||
|
// 2. Read 24 bits of data
|
||||||
|
for (int i = 0; i < 24; i++) {
|
||||||
|
digitalWrite(SCLK_PIN, HIGH);
|
||||||
|
delayMicroseconds(1); // pulse width t3 > 100ns
|
||||||
|
value = (value << 1) | digitalRead(DOUT_PIN);
|
||||||
|
digitalWrite(SCLK_PIN, LOW);
|
||||||
|
delayMicroseconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Configuration Pulses: 27 pulses = Channel A, 128x Gain
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
digitalWrite(SCLK_PIN, HIGH);
|
||||||
|
delayMicroseconds(1);
|
||||||
|
digitalWrite(SCLK_PIN, LOW);
|
||||||
|
delayMicroseconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle 24-bit two's complement
|
||||||
|
if (value & 0x800000) {
|
||||||
|
value |= 0xFF000000;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void calibrateZero() {
|
||||||
|
Serial.println("Taring... stay still.");
|
||||||
|
long sum = 0;
|
||||||
|
for(int i = 0; i < 20; i++) {
|
||||||
|
sum += readADC();
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
offset = sum / 20;
|
||||||
|
Serial.print("New Offset: ");
|
||||||
|
Serial.println(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Check Tare Button
|
||||||
|
if (digitalRead(TARE_BUTTON_PIN) == LOW) {
|
||||||
|
calibrateZero();
|
||||||
|
while(digitalRead(TARE_BUTTON_PIN) == LOW); // Wait for release
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and Filter
|
||||||
|
long raw = readADC();
|
||||||
|
float weight = (float)(raw - offset) * scaleFactor;
|
||||||
|
|
||||||
|
// Serial Output for Debugging / Plotting
|
||||||
|
Serial.print("Raw:");
|
||||||
|
Serial.print(raw);
|
||||||
|
Serial.print("\tWeight:");
|
||||||
|
Serial.print(weight, 2);
|
||||||
|
Serial.println("g");
|
||||||
|
|
||||||
|
delay(100); // 10Hz sampling rate
|
||||||
|
}
|
||||||
11
ScaleHardware/IOT_Scale/test/README
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
5
ScaleHardware/display_driver/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
10
ScaleHardware/display_driver/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
37
ScaleHardware/display_driver/include/README
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
46
ScaleHardware/display_driver/lib/README
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
17
ScaleHardware/display_driver/platformio.ini
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:waveshare_rp2040_zero]
|
||||||
|
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||||
|
board = waveshare_rp2040_zero
|
||||||
|
framework = arduino
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_archive = no
|
||||||
|
lib_deps = bogde/HX711@^0.7.5
|
||||||
50
ScaleHardware/display_driver/src/TM1621_Config.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#ifndef TM1621_CONFIG_H
|
||||||
|
#define TM1621_CONFIG_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
const uint8_t digitMap[] = {
|
||||||
|
0b11111100, // 0
|
||||||
|
0b00000110, // 1
|
||||||
|
0b01011011, // 2
|
||||||
|
0b01001111, // 3
|
||||||
|
0b01100110, // 4
|
||||||
|
0b01101101, // 5
|
||||||
|
0b01111101, // 6
|
||||||
|
0b00000111, // 7
|
||||||
|
0b01111111, // 8
|
||||||
|
0b01101111 // 9
|
||||||
|
};
|
||||||
|
|
||||||
|
const int arrows[] = {283,363,183,203}; //tare,?,?,Zero
|
||||||
|
const int battery[] = {63,83,103}; //LOW / MED / FULL
|
||||||
|
|
||||||
|
// Following your pattern: {A, B, C, D, E, F, G}
|
||||||
|
const int row1_d1[] = {300, 301, 302, 313, 312, 310, 311}; // Addresses 30 & 31
|
||||||
|
const int row1_d2[] = {280, 281, 282, 293, 292, 290, 291}; // Addresses 28 & 29
|
||||||
|
const int row1_d3[] = {260, 261, 262, 273, 272, 270, 271}; // Addresses 26 & 27
|
||||||
|
const int row1_d4[] = {240, 241, 242, 253, 252, 250, 251}; // Addresses 24 & 25
|
||||||
|
const int row1_d5[] = {220, 221, 222, 233, 232, 230, 231}; // Addresses 22 & 23
|
||||||
|
const int row1_decimal[] = {263,232,223}; //XX.X.X.X
|
||||||
|
|
||||||
|
const int row2_d1[] = {200, 201, 202, 213, 212, 210, 211}; // Addresses 20 & 21
|
||||||
|
const int row2_d2[] = {180, 181, 182, 193, 192, 190, 191}; // Addresses 18 & 19
|
||||||
|
const int row2_d3[] = {160, 161, 162, 173, 172, 170, 171}; // Addresses 16 & 17
|
||||||
|
const int row2_d4[] = {140, 141, 142, 153, 152, 150, 151}; // Addresses 14 & 15
|
||||||
|
const int row2_d5[] = {120, 121, 122, 133, 132, 130, 131}; // Addresses 12 & 13
|
||||||
|
const int row2_decimal[] = {163,143,123}; //XX.X.X.X
|
||||||
|
|
||||||
|
const int row3_d1[] = {100, 101, 102, 113, 112, 110, 111}; // Addresses 10 & 11
|
||||||
|
const int row3_d2[] = {80, 81, 82, 93, 92, 90, 91}; // Addresses 8 & 9
|
||||||
|
const int row3_d3[] = {60, 61, 62, 73, 72, 70, 71}; // Addresses 6 & 7
|
||||||
|
const int row3_d4[] = {40, 41, 42, 53, 52, 50, 51}; // Addresses 4 & 5
|
||||||
|
const int row3_d5[] = {20, 21, 22, 33, 32, 30, 31}; // Addresses 2 & 3
|
||||||
|
const int row3_d6[] = {0, 1, 2, 13, 12, 10, 11}; // Addresses 0 & 1
|
||||||
|
const int row3_decimal[] = {43,23,3}; //XX.X.X.X
|
||||||
|
|
||||||
|
const int* digitsRow1[] = {row1_d1, row1_d2, row1_d3, row1_d4, row1_d5};
|
||||||
|
const int* digitsRow2[] = {row2_d1, row2_d2, row2_d3, row2_d4, row2_d5};
|
||||||
|
const int* digitsRow3[] = {row3_d1, row3_d2, row3_d3, row3_d4, row3_d5, row3_d6};
|
||||||
|
|
||||||
|
const int* decimals[] = {row1_decimal, row2_decimal, row3_decimal};
|
||||||
|
#endif
|
||||||
180
ScaleHardware/display_driver/src/main.cpp
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "TM1621_Config.h"
|
||||||
|
|
||||||
|
#define LCD_DATA 29
|
||||||
|
#define LCD_WR 28
|
||||||
|
#define LCD_CS 27
|
||||||
|
|
||||||
|
// Tracking variables
|
||||||
|
int currentAddr = 0;
|
||||||
|
int currentBit = 0;
|
||||||
|
|
||||||
|
void writeBits(uint32_t data, uint8_t count) {
|
||||||
|
for (int8_t i = count - 1; i >= 0; i--) {
|
||||||
|
digitalWrite(LCD_WR, LOW);
|
||||||
|
digitalWrite(LCD_DATA, (data >> i) & 0x01);
|
||||||
|
digitalWrite(LCD_WR, HIGH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendCmd(uint8_t cmd) {
|
||||||
|
digitalWrite(LCD_CS, LOW);
|
||||||
|
writeBits(0x04, 3); // Binary 100 [cite: 432]
|
||||||
|
writeBits(cmd, 8); // Command [cite: 413, 534]
|
||||||
|
writeBits(0, 1); // X bit [cite: 416]
|
||||||
|
digitalWrite(LCD_CS, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeAddr(uint8_t addr, uint8_t data) {
|
||||||
|
digitalWrite(LCD_CS, LOW);
|
||||||
|
uint16_t header = (0x05 << 6) | (addr & 0x3F); // Mode 101 [cite: 475]
|
||||||
|
writeBits(header, 9);
|
||||||
|
writeBits(data & 0x0F, 4); // 4 bits of data [cite: 475]
|
||||||
|
digitalWrite(LCD_CS, HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDisplay() {
|
||||||
|
// Clear all segments first
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
writeAddr(i, 0x00);
|
||||||
|
}
|
||||||
|
// Light up only the current bit
|
||||||
|
writeAddr(currentAddr, (1 << currentBit));
|
||||||
|
|
||||||
|
Serial.print(">>> CURRENT - Address: ");
|
||||||
|
Serial.print(currentAddr);
|
||||||
|
Serial.print(" | Bit (COM): ");
|
||||||
|
Serial.println(currentBit);
|
||||||
|
Serial.println("Enter 'n' for Next, 'p' for Prev:");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
pinMode(LCD_DATA, OUTPUT);
|
||||||
|
pinMode(LCD_WR, OUTPUT);
|
||||||
|
pinMode(LCD_CS, OUTPUT);
|
||||||
|
digitalWrite(LCD_CS, HIGH); // Initialize serial interface [cite: 443]
|
||||||
|
|
||||||
|
Serial.begin(9600);
|
||||||
|
// while (!Serial);
|
||||||
|
|
||||||
|
Serial.println("--- TM1621 Interactive Mapper ---");
|
||||||
|
|
||||||
|
sendCmd(0x01); // SYS EN [cite: 534]
|
||||||
|
sendCmd(0x29); // BIAS 1/3, 4 COM [cite: 420, 544]
|
||||||
|
sendCmd(0x03); // LCD ON [cite: 534]
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t shadowRAM[32] = {0};
|
||||||
|
|
||||||
|
void writeMappedSegment(int aab, bool state) {
|
||||||
|
int addr = aab / 10;
|
||||||
|
int bit = aab % 10;
|
||||||
|
|
||||||
|
if (state) shadowRAM[addr] |= (1 << bit);
|
||||||
|
else shadowRAM[addr] &= ~(1 << bit);
|
||||||
|
|
||||||
|
writeAddr(addr, shadowRAM[addr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayDigit(const int segments[], int number) {
|
||||||
|
uint8_t bits = digitMap[number % 10];
|
||||||
|
for (int i = 0; i < 7; i++) {
|
||||||
|
// We check Bit 0, then Bit 1, etc.
|
||||||
|
// This maps i=0 to Segment A, i=1 to Segment B...
|
||||||
|
bool state = (bits >> i) & 0x01;
|
||||||
|
writeMappedSegment(segments[i], state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printToRow(int row, long value, int decimalPos = 0) {
|
||||||
|
const int** currentRow;
|
||||||
|
int numDigits;
|
||||||
|
|
||||||
|
// Select row configuration
|
||||||
|
switch(row) {
|
||||||
|
case 1: currentRow = digitsRow1; numDigits = 5; break;
|
||||||
|
case 2: currentRow = digitsRow2; numDigits = 5; break;
|
||||||
|
case 3: currentRow = digitsRow3; numDigits = 6; break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the number right-aligned
|
||||||
|
long tempValue = value;
|
||||||
|
for (int i = numDigits - 1; i >= 0; i--) {
|
||||||
|
if (tempValue > 0 || i == numDigits - 1) { // Show at least one digit
|
||||||
|
displayDigit(currentRow[i], tempValue % 10);
|
||||||
|
tempValue /= 10;
|
||||||
|
} else {
|
||||||
|
// Clear leading zeros (all segments off)
|
||||||
|
for (int s = 0; s < 7; s++) writeMappedSegment(currentRow[i][s], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Decimal Points (if applicable for that row)
|
||||||
|
if (decimalPos > 0 && decimalPos <= 3) {
|
||||||
|
writeMappedSegment(decimals[row-1][decimalPos-1], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int counter1 = 0;
|
||||||
|
int counter2 = 0;
|
||||||
|
int counter3 = 0;
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
printToRow(1, counter1);
|
||||||
|
printToRow(2, counter2);
|
||||||
|
printToRow(3, counter3);
|
||||||
|
|
||||||
|
counter1 = (counter1 + 1) % 1000;
|
||||||
|
counter2 = (counter2 + 2) % 1000;
|
||||||
|
counter3 = (counter3 + 3) % 1000;
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// void loop() {
|
||||||
|
// writeAddr(0, 0x00);
|
||||||
|
// writeAddr(1, 0x00);
|
||||||
|
// writeAddr(2, 0x00);
|
||||||
|
// writeAddr(3, 0x00);
|
||||||
|
|
||||||
|
// displayDigit(row1_d1, counter);
|
||||||
|
|
||||||
|
// Serial.println(counter);
|
||||||
|
|
||||||
|
// counter++;
|
||||||
|
// if (counter > 9) {
|
||||||
|
// counter = 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// delay(1000);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void loop() {
|
||||||
|
// if (Serial.available() > 0) {
|
||||||
|
// char input = Serial.read();
|
||||||
|
|
||||||
|
// // Ignore newline/carriage return characters
|
||||||
|
// if (input == '\n' || input == '\r') return;
|
||||||
|
|
||||||
|
// if (input == 'n' || input == 'N') {
|
||||||
|
// currentBit++;
|
||||||
|
// if (currentBit > 3) {
|
||||||
|
// currentBit = 0;
|
||||||
|
// currentAddr++;
|
||||||
|
// }
|
||||||
|
// if (currentAddr > 31) currentAddr = 0;
|
||||||
|
// updateDisplay();
|
||||||
|
// }
|
||||||
|
// else if (input == 'p' || input == 'P') {
|
||||||
|
// currentBit--;
|
||||||
|
// if (currentBit < 0) {
|
||||||
|
// currentBit = 3;
|
||||||
|
// currentAddr--;
|
||||||
|
// }
|
||||||
|
// if (currentAddr < 0) currentAddr = 31;
|
||||||
|
// updateDisplay();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
11
ScaleHardware/display_driver/test/README
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
@@ -2,6 +2,42 @@ module ScannerGO
|
|||||||
|
|
||||||
go 1.25.7
|
go 1.25.7
|
||||||
|
|
||||||
require github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
require (
|
||||||
|
fyne.io/fyne/v2 v2.7.3
|
||||||
|
github.com/google/gousb v1.1.3
|
||||||
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.41.0 // indirect
|
require (
|
||||||
|
fyne.io/systray v1.12.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/fredbi/uri v1.1.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||||
|
github.com/fyne-io/glfw-js v0.3.0 // indirect
|
||||||
|
github.com/fyne-io/image v0.1.1 // indirect
|
||||||
|
github.com/fyne-io/oksvg v0.2.0 // indirect
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||||
|
github.com/go-text/render v0.2.0 // indirect
|
||||||
|
github.com/go-text/typesetting v0.3.3 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||||
|
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rymdport/portal v0.4.2 // indirect
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
|
golang.org/x/image v0.24.0 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,82 @@
|
|||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
fyne.io/fyne/v2 v2.7.3 h1:xBT/iYbdnNHONWO38fZMBrVBiJG8rV/Jypmy4tVfRWE=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
fyne.io/fyne/v2 v2.7.3/go.mod h1:gu+dlIcZWSzKZmnrY8Fbnj2Hirabv2ek+AKsfQ2bBlw=
|
||||||
|
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
||||||
|
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||||
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
|
github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
|
||||||
|
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
||||||
|
github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||||
|
github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk=
|
||||||
|
github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||||
|
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||||
|
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||||
|
github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8=
|
||||||
|
github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||||
|
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||||
|
github.com/go-text/typesetting v0.3.3 h1:ihGNJU9KzdK2QRDy1Bm7FT5RFQoYb+3n3EIhI/4eaQc=
|
||||||
|
github.com/go-text/typesetting v0.3.3/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/google/gousb v1.1.3 h1:xt6M5TDsGSZ+rlomz5Si5Hmd/Fvbmo2YCJHN+yGaK4o=
|
||||||
|
github.com/google/gousb v1.1.3/go.mod h1:GGWUkK0gAXDzxhwrzetW592aOmkkqSGcj5KLEgmCVUg=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||||
|
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||||
|
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||||
|
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
||||||
|
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,159 +1,229 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"image/color"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tarm/serial"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/app"
|
||||||
|
"fyne.io/fyne/v2/canvas"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/google/gousb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port string `json:"port"`
|
TargetURL string `json:"target_url"`
|
||||||
URL string `json:"url"`
|
VendorID uint16 `json:"vendor_id"`
|
||||||
BaudRate int `json:"baud"`
|
ProductID uint16 `json:"product_id"`
|
||||||
Delimiter string `json:"delimiter"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultConfig = Config{
|
var hidMap = map[byte]string{
|
||||||
Port: "/dev/ttyACM0",
|
4: "a", 5: "b", 6: "c", 7: "d", 8: "e", 9: "f", 10: "g", 11: "h", 12: "i",
|
||||||
URL: "https://scanner.sekidesu.xyz/scan",
|
13: "j", 14: "k", 15: "l", 16: "m", 17: "n", 18: "o", 19: "p", 20: "q",
|
||||||
BaudRate: 115200,
|
21: "r", 22: "s", 23: "t", 24: "u", 25: "v", 26: "w", 27: "x", 28: "y", 29: "z",
|
||||||
Delimiter: "\n",
|
30: "1", 31: "2", 32: "3", 33: "4", 34: "5", 35: "6", 36: "7", 37: "8", 38: "9", 39: "0",
|
||||||
|
44: " ", 45: "-", 46: "=", 55: ".", 56: "/",
|
||||||
}
|
}
|
||||||
|
|
||||||
const configPath = "config.json"
|
type BridgeApp struct {
|
||||||
|
urlEntry *widget.Entry
|
||||||
func main() {
|
status *canvas.Text
|
||||||
cfg := loadConfig()
|
logList *widget.List
|
||||||
|
logs []string
|
||||||
portName := flag.String("port", cfg.Port, "Serial port name")
|
window fyne.Window
|
||||||
endpoint := flag.String("url", cfg.URL, "Target URL endpoint")
|
config Config
|
||||||
baudRate := flag.Int("baud", cfg.BaudRate, "Baud rate")
|
isCLI bool
|
||||||
delim := flag.String("delim", cfg.Delimiter, "Line delimiter")
|
|
||||||
save := flag.Bool("save", false, "Save current parameters to config.json")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
cfg.Port = *portName
|
|
||||||
cfg.URL = *endpoint
|
|
||||||
cfg.BaudRate = *baudRate
|
|
||||||
cfg.Delimiter = *delim
|
|
||||||
|
|
||||||
if *save {
|
|
||||||
saveConfig(cfg)
|
|
||||||
fmt.Println("Settings saved to", configPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
serialConfig := &serial.Config{
|
|
||||||
Name: cfg.Port,
|
|
||||||
Baud: cfg.BaudRate,
|
|
||||||
ReadTimeout: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
port, err := serial.OpenPort(serialConfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error opening port %s: %v\n", cfg.Port, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer port.Close()
|
|
||||||
|
|
||||||
fmt.Printf("Listening on %s (Baud: %d, Delim: %q)...\n", cfg.Port, cfg.BaudRate, cfg.Delimiter)
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(port)
|
|
||||||
|
|
||||||
// Custom split function to handle the configurable delimiter
|
|
||||||
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
||||||
if atEOF && len(data) == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
if i := bytes.Index(data, []byte(cfg.Delimiter)); i >= 0 {
|
|
||||||
return i + len(cfg.Delimiter), data[0:i], nil
|
|
||||||
}
|
|
||||||
if atEOF {
|
|
||||||
return len(data), data, nil
|
|
||||||
}
|
|
||||||
return 0, nil, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
content := strings.TrimSpace(scanner.Text())
|
|
||||||
if content != "" {
|
|
||||||
sendToEndpoint(cfg.URL, content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
fmt.Printf("Scanner error: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig() Config {
|
func loadConfig() Config {
|
||||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
conf := Config{
|
||||||
saveConfig(defaultConfig)
|
TargetURL: "https://scanner.sekidesu.xyz/scan",
|
||||||
return defaultConfig
|
VendorID: 0xFFFF,
|
||||||
|
ProductID: 0x0035,
|
||||||
}
|
}
|
||||||
|
file, err := os.ReadFile("config.json")
|
||||||
file, err := os.Open(configPath)
|
if err == nil {
|
||||||
if err != nil {
|
json.Unmarshal(file, &conf)
|
||||||
return defaultConfig
|
} else {
|
||||||
|
// Create default config if missing
|
||||||
|
data, _ := json.MarshalIndent(conf, "", " ")
|
||||||
|
os.WriteFile("config.json", data, 0644)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
return conf
|
||||||
|
|
||||||
var cfg Config
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
if err := decoder.Decode(&cfg); err != nil {
|
|
||||||
return defaultConfig
|
|
||||||
}
|
|
||||||
// Handle case where JSON exists but field is missing
|
|
||||||
if cfg.Delimiter == "" {
|
|
||||||
cfg.Delimiter = "\n"
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveConfig(cfg Config) {
|
func main() {
|
||||||
file, err := os.Create(configPath)
|
cliMode := flag.Bool("cli", false, "Run in CLI mode without GUI")
|
||||||
if err != nil {
|
flag.Parse()
|
||||||
fmt.Printf("Failed to create/save config: %v\n", err)
|
|
||||||
|
conf := loadConfig()
|
||||||
|
bridge := &BridgeApp{
|
||||||
|
config: conf,
|
||||||
|
isCLI: *cliMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if *cliMode {
|
||||||
|
fmt.Println("Running in CLI mode...")
|
||||||
|
bridge.usbListenLoop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(file)
|
a := app.New()
|
||||||
encoder.SetIndent("", " ")
|
w := a.NewWindow("POS Hardware Bridge (Go)")
|
||||||
encoder.Encode(cfg)
|
bridge.window = w
|
||||||
|
bridge.urlEntry = widget.NewEntry()
|
||||||
|
bridge.urlEntry.SetText(conf.TargetURL)
|
||||||
|
bridge.status = canvas.NewText("Status: Booting...", color.Black)
|
||||||
|
bridge.status.TextSize = 14
|
||||||
|
|
||||||
|
bridge.logList = widget.NewList(
|
||||||
|
func() int { return len(bridge.logs) },
|
||||||
|
func() fyne.CanvasObject { return widget.NewLabel("template") },
|
||||||
|
func(i widget.ListItemID, o fyne.CanvasObject) {
|
||||||
|
o.(*widget.Label).SetText(bridge.logs[i])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
content := container.NewBorder(
|
||||||
|
container.NewVBox(
|
||||||
|
widget.NewLabel("Target POS Endpoint:"),
|
||||||
|
bridge.urlEntry,
|
||||||
|
bridge.status,
|
||||||
|
widget.NewLabel("Activity Log:"),
|
||||||
|
),
|
||||||
|
nil, nil, nil,
|
||||||
|
bridge.logList,
|
||||||
|
)
|
||||||
|
|
||||||
|
w.SetContent(content)
|
||||||
|
w.Resize(fyne.NewSize(500, 400))
|
||||||
|
|
||||||
|
go bridge.usbListenLoop()
|
||||||
|
w.ShowAndRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendToEndpoint(baseURL, content string) {
|
func (b *BridgeApp) addLog(msg string) {
|
||||||
client := &http.Client{
|
ts := time.Now().Format("15:04:05")
|
||||||
Timeout: 5 * time.Second,
|
formatted := fmt.Sprintf("[%s] %s", ts, msg)
|
||||||
|
|
||||||
|
if b.isCLI {
|
||||||
|
fmt.Println(formatted)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fullURL := fmt.Sprintf("%s?content=%s", baseURL, url.QueryEscape(content))
|
fyne.DoAndWait(func() {
|
||||||
resp, err := client.Get(fullURL)
|
b.logs = append([]string{formatted}, b.logs...)
|
||||||
|
if len(b.logs) > 15 {
|
||||||
|
b.logs = b.logs[:15]
|
||||||
|
}
|
||||||
|
b.logList.Refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BridgeApp) updateStatus(msg string, col color.Color) {
|
||||||
|
if b.isCLI {
|
||||||
|
fmt.Printf("STATUS: %s\n", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fyne.DoAndWait(func() {
|
||||||
|
b.status.Text = msg
|
||||||
|
b.status.Color = col
|
||||||
|
b.status.Refresh()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BridgeApp) sendToPos(barcode string) {
|
||||||
|
url := b.config.TargetURL
|
||||||
|
if !b.isCLI {
|
||||||
|
url = b.urlEntry.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
b.addLog(fmt.Sprintf("Captured: %s. Sending to %s", barcode, url))
|
||||||
|
client := http.Client{Timeout: 3 * time.Second}
|
||||||
|
resp, err := client.Get(url + "?content=" + barcode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Network Error: %v\n", err)
|
b.addLog("HTTP Error: Backend unreachable")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
b.addLog(fmt.Sprintf("Success: POS returned %d", resp.StatusCode))
|
||||||
body, err := io.ReadAll(resp.Body)
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error reading response: %v\n", err)
|
func (b *BridgeApp) usbListenLoop() {
|
||||||
return
|
ctx := gousb.NewContext()
|
||||||
}
|
defer ctx.Close()
|
||||||
|
|
||||||
fmt.Printf("Data: [%s] | Status: %s\n", content, resp.Status)
|
for {
|
||||||
if len(body) > 0 {
|
dev, err := ctx.OpenDeviceWithVIDPID(gousb.ID(b.config.VendorID), gousb.ID(b.config.ProductID))
|
||||||
fmt.Printf("Response: %s\n", string(body))
|
|
||||||
}
|
if err != nil || dev == nil {
|
||||||
fmt.Println(strings.Repeat("-", 30))
|
b.updateStatus("Scanner unplugged. Waiting...", color.NRGBA{200, 0, 0, 255})
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b.updateStatus("Scanner Locked & Ready", color.NRGBA{0, 180, 0, 255})
|
||||||
|
|
||||||
|
intf, done, err := dev.DefaultInterface()
|
||||||
|
if err != nil {
|
||||||
|
b.addLog("Error claiming interface")
|
||||||
|
dev.Close()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var inEp *gousb.InEndpoint
|
||||||
|
for _, epDesc := range intf.Setting.Endpoints {
|
||||||
|
if epDesc.Direction == gousb.EndpointDirectionIn {
|
||||||
|
inEp, _ = intf.InEndpoint(epDesc.Number)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inEp == nil {
|
||||||
|
b.addLog("No IN endpoint found")
|
||||||
|
done()
|
||||||
|
dev.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBarcode := ""
|
||||||
|
buf := make([]byte, inEp.Desc.MaxPacketSize)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := inEp.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if n < 3 || buf[2] == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier := buf[0]
|
||||||
|
keycode := buf[2]
|
||||||
|
isShift := (modifier == 2 || modifier == 32)
|
||||||
|
|
||||||
|
if keycode == 40 {
|
||||||
|
if currentBarcode != "" {
|
||||||
|
go b.sendToPos(currentBarcode)
|
||||||
|
currentBarcode = ""
|
||||||
|
}
|
||||||
|
} else if val, ok := hidMap[keycode]; ok {
|
||||||
|
if isShift && len(val) == 1 && val[0] >= 'a' && val[0] <= 'z' {
|
||||||
|
val = string(val[0] - 32)
|
||||||
|
}
|
||||||
|
currentBarcode += val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
done()
|
||||||
|
dev.Close()
|
||||||
|
b.addLog("Hardware connection lost. Reconnecting...")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
402
app.py
@@ -1,12 +1,24 @@
|
|||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import requests
|
import requests
|
||||||
|
from flask import send_file
|
||||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_from_directory
|
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_from_directory
|
||||||
from flask_socketio import SocketIO, emit
|
from flask_socketio import SocketIO, emit
|
||||||
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
import zipfile
|
||||||
|
import io
|
||||||
|
|
||||||
|
# from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# load_dotenv()
|
||||||
|
|
||||||
|
# MP_ACCESS_TOKEN = os.getenv('MP_ACCESS_TOKEN')
|
||||||
|
# MP_TERMINAL_ID = os.getenv('MP_TERMINAL_ID')
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
|
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
|
||||||
@@ -31,10 +43,41 @@ def init_db():
|
|||||||
with sqlite3.connect(DB_FILE) as conn:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS users
|
conn.execute('''CREATE TABLE IF NOT EXISTS users
|
||||||
(id INTEGER PRIMARY KEY, username TEXT UNIQUE, password TEXT)''')
|
(id INTEGER PRIMARY KEY, username TEXT UNIQUE, password TEXT)''')
|
||||||
|
# Updated table definition
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS products
|
conn.execute('''CREATE TABLE IF NOT EXISTS products
|
||||||
(barcode TEXT PRIMARY KEY, name TEXT, price REAL, image_url TEXT)''')
|
(barcode TEXT PRIMARY KEY,
|
||||||
|
name TEXT,
|
||||||
|
price REAL,
|
||||||
|
image_url TEXT,
|
||||||
|
stock REAL DEFAULT 0,
|
||||||
|
unit_type TEXT DEFAULT 'unit')''')
|
||||||
|
|
||||||
# Default user: admin / Pass: choripan1234
|
# Add these two tables for sales history
|
||||||
|
conn.execute('''CREATE TABLE IF NOT EXISTS sales
|
||||||
|
(id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
total REAL,
|
||||||
|
payment_method TEXT)''')
|
||||||
|
|
||||||
|
conn.execute('''CREATE TABLE IF NOT EXISTS sale_items
|
||||||
|
(id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
sale_id INTEGER,
|
||||||
|
barcode TEXT,
|
||||||
|
name TEXT,
|
||||||
|
price REAL,
|
||||||
|
quantity REAL,
|
||||||
|
subtotal REAL,
|
||||||
|
FOREIGN KEY(sale_id) REFERENCES sales(id))''')
|
||||||
|
|
||||||
|
conn.execute('''CREATE TABLE IF NOT EXISTS dicom
|
||||||
|
(id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT UNIQUE,
|
||||||
|
amount REAL DEFAULT 0,
|
||||||
|
notes TEXT,
|
||||||
|
image_url TEXT,
|
||||||
|
last_updated TEXT DEFAULT CURRENT_TIMESTAMP)''')
|
||||||
|
|
||||||
|
# Default user logic remains same...
|
||||||
user = conn.execute('SELECT * FROM users WHERE username = ?', ('admin',)).fetchone()
|
user = conn.execute('SELECT * FROM users WHERE username = ?', ('admin',)).fetchone()
|
||||||
if not user:
|
if not user:
|
||||||
hashed_pw = generate_password_hash('choripan1234')
|
hashed_pw = generate_password_hash('choripan1234')
|
||||||
@@ -105,23 +148,70 @@ def login():
|
|||||||
user = conn.execute('SELECT * FROM users WHERE username = ?', (user_in,)).fetchone()
|
user = conn.execute('SELECT * FROM users WHERE username = ?', (user_in,)).fetchone()
|
||||||
if user and check_password_hash(user[2], pass_in):
|
if user and check_password_hash(user[2], pass_in):
|
||||||
login_user(User(user[0], user[1]))
|
login_user(User(user[0], user[1]))
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('inventory'))
|
||||||
flash('Invalid credentials.')
|
flash('Invalid credentials.')
|
||||||
return render_template('login.html')
|
return render_template('login.html')
|
||||||
|
|
||||||
@app.route('/logout')
|
@app.route('/logout')
|
||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
logout_user(); return redirect(url_for('login'))
|
logout_user()
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@login_required
|
@login_required
|
||||||
def index():
|
def defaultRoute():
|
||||||
|
return redirect(url_for('inventory'))
|
||||||
|
|
||||||
|
@app.route('/inventory')
|
||||||
|
@login_required
|
||||||
|
def inventory():
|
||||||
with sqlite3.connect(DB_FILE) as conn:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
products = conn.execute('SELECT * FROM products').fetchall()
|
products = conn.execute('SELECT * FROM products').fetchall()
|
||||||
return render_template('index.html', products=products, user=current_user)
|
return render_template('inventory.html', active_page='inventory', products=products, user=current_user)
|
||||||
|
|
||||||
@app.route('/upsert', methods=['POST'])
|
@app.route("/checkout")
|
||||||
|
@login_required
|
||||||
|
def checkout():
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
# Fetching the same columns the scanner expects
|
||||||
|
products = conn.execute('SELECT barcode, name, price, image_url, stock, unit_type FROM products').fetchall()
|
||||||
|
return render_template("checkout.html", active_page='checkout', user=current_user, products=products)
|
||||||
|
|
||||||
|
@app.route('/dicom')
|
||||||
|
@login_required
|
||||||
|
def dicom():
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
debtors = conn.execute('SELECT id, name, amount, notes, datetime(last_updated, "localtime"), image_url FROM dicom ORDER BY amount DESC').fetchall()
|
||||||
|
return render_template('dicom.html', active_page='dicom', user=current_user, debtors=debtors)
|
||||||
|
|
||||||
|
@app.route('/sales')
|
||||||
|
@login_required
|
||||||
|
def sales():
|
||||||
|
selected_date = request.args.get('date')
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Determine the target date for the "Daily" stat
|
||||||
|
target_date = selected_date if selected_date else cur.execute("SELECT date('now', 'localtime')").fetchone()[0]
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
"daily": cur.execute("SELECT SUM(total) FROM sales WHERE date(date, 'localtime') = ?", (target_date,)).fetchone()[0] or 0,
|
||||||
|
"week": cur.execute("SELECT SUM(total) FROM sales WHERE date(date, 'localtime') >= date('now', 'localtime', '-7 days')").fetchone()[0] or 0,
|
||||||
|
"month": cur.execute("SELECT SUM(total) FROM sales WHERE strftime('%Y-%m', date, 'localtime') = strftime('%Y-%m', 'now', 'localtime')").fetchone()[0] or 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if selected_date:
|
||||||
|
sales_data = cur.execute('''SELECT id, date, total, payment_method FROM sales
|
||||||
|
WHERE date(date, 'localtime') = ?
|
||||||
|
ORDER BY date DESC''', (selected_date,)).fetchall()
|
||||||
|
else:
|
||||||
|
sales_data = cur.execute('SELECT id, date, total, payment_method FROM sales ORDER BY date DESC LIMIT 100').fetchall()
|
||||||
|
|
||||||
|
return render_template('sales.html', active_page='sales', user=current_user, sales=sales_data, stats=stats, selected_date=selected_date)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/upsert", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def upsert():
|
def upsert():
|
||||||
d = request.form
|
d = request.form
|
||||||
@@ -129,18 +219,27 @@ def upsert():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
price = float(d['price'])
|
price = float(d['price'])
|
||||||
|
stock = float(d.get('stock', 0)) # New field
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
price = 0.0
|
price = 0.0
|
||||||
|
stock = 0.0
|
||||||
|
|
||||||
|
unit_type = d.get('unit_type', 'unit') # New field (unit or kg)
|
||||||
final_image_path = download_image(d['image_url'], barcode)
|
final_image_path = download_image(d['image_url'], barcode)
|
||||||
|
|
||||||
with sqlite3.connect(DB_FILE) as conn:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
conn.execute('''INSERT INTO products (barcode, name, price, image_url) VALUES (?,?,?,?)
|
# Updated UPSERT query
|
||||||
ON CONFLICT(barcode) DO UPDATE SET name=excluded.name,
|
conn.execute('''INSERT INTO products (barcode, name, price, image_url, stock, unit_type)
|
||||||
price=excluded.price, image_url=excluded.image_url''',
|
VALUES (?,?,?,?,?,?)
|
||||||
(barcode, d['name'], price, final_image_path))
|
ON CONFLICT(barcode) DO UPDATE SET
|
||||||
|
name=excluded.name,
|
||||||
|
price=excluded.price,
|
||||||
|
image_url=excluded.image_url,
|
||||||
|
stock=excluded.stock,
|
||||||
|
unit_type=excluded.unit_type''',
|
||||||
|
(barcode, d['name'], price, final_image_path, stock, unit_type))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('inventory'))
|
||||||
|
|
||||||
@app.route('/delete/<barcode>', methods=['POST'])
|
@app.route('/delete/<barcode>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@@ -152,7 +251,7 @@ def delete(barcode):
|
|||||||
img_p = os.path.join(CACHE_DIR, f"{barcode}.jpg")
|
img_p = os.path.join(CACHE_DIR, f"{barcode}.jpg")
|
||||||
if os.path.exists(img_p): os.remove(img_p)
|
if os.path.exists(img_p): os.remove(img_p)
|
||||||
socketio.emit('product_deleted', {"barcode": barcode})
|
socketio.emit('product_deleted', {"barcode": barcode})
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('inventory'))
|
||||||
|
|
||||||
@app.route('/scan', methods=['GET'])
|
@app.route('/scan', methods=['GET'])
|
||||||
def scan():
|
def scan():
|
||||||
@@ -161,13 +260,13 @@ def scan():
|
|||||||
return jsonify({"status": "error", "message": "empty barcode"}), 400
|
return jsonify({"status": "error", "message": "empty barcode"}), 400
|
||||||
|
|
||||||
with sqlite3.connect(DB_FILE) as conn:
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
p = conn.execute('SELECT * FROM products WHERE barcode = ?', (barcode,)).fetchone()
|
# Fixed: Selecting all 6 necessary columns
|
||||||
|
p = conn.execute('SELECT barcode, name, price, image_url, stock, unit_type FROM products WHERE barcode = ?', (barcode,)).fetchone()
|
||||||
|
|
||||||
# 1. Product exists in local Database
|
|
||||||
if p:
|
if p:
|
||||||
barcode_val, name, price, image_path = p
|
# Now matches the 6 columns in the SELECT statement
|
||||||
|
barcode_val, name, price, image_path, stock, unit_type = p
|
||||||
|
|
||||||
# Image recovery logic for missing local files
|
|
||||||
if image_path and image_path.startswith('/static/'):
|
if image_path and image_path.startswith('/static/'):
|
||||||
clean_path = image_path.split('?')[0].lstrip('/')
|
clean_path = image_path.split('?')[0].lstrip('/')
|
||||||
if not os.path.exists(clean_path):
|
if not os.path.exists(clean_path):
|
||||||
@@ -182,7 +281,9 @@ def scan():
|
|||||||
"barcode": barcode_val,
|
"barcode": barcode_val,
|
||||||
"name": name,
|
"name": name,
|
||||||
"price": int(price),
|
"price": int(price),
|
||||||
"image": image_path
|
"image": image_path,
|
||||||
|
"stock": stock,
|
||||||
|
"unit_type": unit_type
|
||||||
}
|
}
|
||||||
|
|
||||||
socketio.emit('new_scan', product_data)
|
socketio.emit('new_scan', product_data)
|
||||||
@@ -266,13 +367,274 @@ def upload_image():
|
|||||||
barcode = request.form['barcode']
|
barcode = request.form['barcode']
|
||||||
if file.filename == '' or not barcode:
|
if file.filename == '' or not barcode:
|
||||||
return jsonify({"error": "Invalid data"}), 400
|
return jsonify({"error": "Invalid data"}), 400
|
||||||
ext = mimetypes.guess_extension(file.mimetype) or '.jpg'
|
|
||||||
filename = f"{barcode}{ext}"
|
filename = f"{barcode}.jpg"
|
||||||
filepath = os.path.join(CACHE_DIR, filename)
|
filepath = os.path.join(CACHE_DIR, filename)
|
||||||
file.save(filepath)
|
file.save(filepath)
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
return jsonify({"status": "success", "image_url": f"/static/cache/{filename}?t={timestamp}"}), 200
|
return jsonify({"status": "success", "image_url": f"/static/cache/{filename}?t={timestamp}"}), 200
|
||||||
|
|
||||||
|
@app.route('/api/scale/weight', methods=['POST'])
|
||||||
|
def update_scale_weight():
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
# Assuming the scale sends {"weight": 1250} (in grams)
|
||||||
|
weight_grams = data.get('weight', 0)
|
||||||
|
|
||||||
|
# Optional: Convert to kg if you prefer
|
||||||
|
weight_kg = round(weight_grams / 1000, 3)
|
||||||
|
|
||||||
|
# Broadcast to all connected clients via SocketIO
|
||||||
|
socketio.emit('scale_update', {
|
||||||
|
"grams": weight_grams,
|
||||||
|
"kilograms": weight_kg,
|
||||||
|
"timestamp": time.time()
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({"status": "received"}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/checkout', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def process_checkout():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
cart = data.get('cart', [])
|
||||||
|
payment_method = data.get('payment_method', 'efectivo')
|
||||||
|
|
||||||
|
if not cart:
|
||||||
|
return jsonify({"error": "Cart is empty"}), 400
|
||||||
|
|
||||||
|
# Recalculate total on the server because the frontend is a liar
|
||||||
|
total = sum(item.get('subtotal', 0) for item in cart)
|
||||||
|
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Let SQLite handle the exact UTC timestamp internally
|
||||||
|
cur.execute('INSERT INTO sales (date, total, payment_method) VALUES (CURRENT_TIMESTAMP, ?, ?)', (total, payment_method))
|
||||||
|
sale_id = cur.lastrowid
|
||||||
|
|
||||||
|
# Record each item and deduct stock
|
||||||
|
for item in cart:
|
||||||
|
cur.execute('''INSERT INTO sale_items (sale_id, barcode, name, price, quantity, subtotal)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)''',
|
||||||
|
(sale_id, item['barcode'], item['name'], item['price'], item['qty'], item['subtotal']))
|
||||||
|
|
||||||
|
# Deduct from inventory (Manual products will safely be ignored here)
|
||||||
|
cur.execute('UPDATE products SET stock = stock - ? WHERE barcode = ?', (item['qty'], item['barcode']))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return jsonify({"status": "success", "sale_id": sale_id}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Checkout Error: {e}")
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/sale/<int:sale_id>')
|
||||||
|
@login_required
|
||||||
|
def get_sale_details(sale_id):
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
items = conn.execute('SELECT barcode, name, price, quantity, subtotal FROM sale_items WHERE sale_id = ?', (sale_id,)).fetchall()
|
||||||
|
|
||||||
|
# Format it as a neat list of dictionaries for JavaScript to digest
|
||||||
|
item_list = [{"barcode": i[0], "name": i[1], "price": i[2], "qty": i[3], "subtotal": i[4]} for i in items]
|
||||||
|
return jsonify(item_list), 200
|
||||||
|
|
||||||
|
@app.route('/api/sale/<int:sale_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def reverse_sale(sale_id):
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 1. Get the items and quantities from the receipt
|
||||||
|
items = cur.execute('SELECT barcode, quantity FROM sale_items WHERE sale_id = ?', (sale_id,)).fetchall()
|
||||||
|
|
||||||
|
# 2. Add the stock back to the inventory
|
||||||
|
for barcode, qty in items:
|
||||||
|
# This safely ignores manual products since their fake barcodes won't match any row
|
||||||
|
cur.execute('UPDATE products SET stock = stock + ? WHERE barcode = ?', (qty, barcode))
|
||||||
|
|
||||||
|
# 3. Destroy the evidence
|
||||||
|
cur.execute('DELETE FROM sale_items WHERE sale_id = ?', (sale_id,))
|
||||||
|
cur.execute('DELETE FROM sales WHERE id = ?', (sale_id,))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
return jsonify({"status": "success"}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Reverse Sale Error: {e}")
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/dicom/update', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def update_dicom():
|
||||||
|
data = request.get_json()
|
||||||
|
name = data.get('name', '').strip()
|
||||||
|
amount = float(data.get('amount', 0))
|
||||||
|
notes = data.get('notes', '')
|
||||||
|
image_url = data.get('image_url', '')
|
||||||
|
action = data.get('action')
|
||||||
|
|
||||||
|
if not name or amount <= 0:
|
||||||
|
return jsonify({"error": "Nombre y monto válidos son requeridos"}), 400
|
||||||
|
|
||||||
|
if action == 'add':
|
||||||
|
amount = -amount
|
||||||
|
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute('''INSERT INTO dicom (name, amount, notes, image_url, last_updated)
|
||||||
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||||
|
ON CONFLICT(name) DO UPDATE SET
|
||||||
|
amount = amount + excluded.amount,
|
||||||
|
notes = excluded.notes,
|
||||||
|
image_url = CASE WHEN excluded.image_url != "" THEN excluded.image_url ELSE dicom.image_url END,
|
||||||
|
last_updated = CURRENT_TIMESTAMP''', (name, amount, notes, image_url))
|
||||||
|
conn.commit()
|
||||||
|
return jsonify({"status": "success"}), 200
|
||||||
|
|
||||||
|
@app.route('/api/dicom/<int:debtor_id>', methods=['DELETE'])
|
||||||
|
@login_required
|
||||||
|
def delete_dicom(debtor_id):
|
||||||
|
try:
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
conn.execute('DELETE FROM dicom WHERE id = ?', (debtor_id,))
|
||||||
|
conn.commit()
|
||||||
|
return jsonify({"status": "success"}), 200
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/settings/update', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def update_settings():
|
||||||
|
new_password = request.form.get('password')
|
||||||
|
profile_pic = request.form.get('profile_pic')
|
||||||
|
|
||||||
|
with sqlite3.connect(DB_FILE) as conn:
|
||||||
|
if new_password and len(new_password) > 0:
|
||||||
|
hashed_pw = generate_password_hash(new_password)
|
||||||
|
conn.execute('UPDATE users SET password = ? WHERE id = ?', (hashed_pw, current_user.id))
|
||||||
|
|
||||||
|
if profile_pic:
|
||||||
|
conn.execute('UPDATE users SET profile_pic = ? WHERE id = ?', (profile_pic, current_user.id))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
flash('Configuración actualizada')
|
||||||
|
return redirect(request.referrer)
|
||||||
|
|
||||||
|
@app.route('/export/db')
|
||||||
|
@login_required
|
||||||
|
def export_db():
|
||||||
|
if os.path.exists(DB_FILE):
|
||||||
|
return send_file(DB_FILE, as_attachment=True, download_name=f"SekiPOS_Backup_{datetime.now().strftime('%Y%m%d')}.db", mimetype='application/x-sqlite3')
|
||||||
|
return "Error: Database file not found", 404
|
||||||
|
|
||||||
|
@app.route('/export/images')
|
||||||
|
@login_required
|
||||||
|
def export_images():
|
||||||
|
if not os.path.exists(CACHE_DIR) or not os.listdir(CACHE_DIR):
|
||||||
|
return "No images found to export", 404
|
||||||
|
|
||||||
|
# Create an in-memory byte stream to hold the zip data
|
||||||
|
memory_file = io.BytesIO()
|
||||||
|
|
||||||
|
with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
for root, dirs, files in os.walk(CACHE_DIR):
|
||||||
|
for file in files:
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
# Store files using their names only to avoid nesting inside the zip
|
||||||
|
zf.write(file_path, arcname=file)
|
||||||
|
|
||||||
|
memory_file.seek(0)
|
||||||
|
|
||||||
|
return send_file(
|
||||||
|
memory_file,
|
||||||
|
mimetype='application/zip',
|
||||||
|
as_attachment=True,
|
||||||
|
download_name=f"SekiPOS_Images_{datetime.now().strftime('%Y%m%d')}.zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
# @app.route('/process_payment', methods=['POST'])
|
||||||
|
# @login_required
|
||||||
|
# def process_payment():
|
||||||
|
# data = request.get_json()
|
||||||
|
# total_amount = data.get('total')
|
||||||
|
|
||||||
|
# if not total_amount or total_amount <= 0:
|
||||||
|
# return jsonify({"error": "Invalid amount"}), 400
|
||||||
|
|
||||||
|
# url = "https://api.mercadopago.com/v1/orders"
|
||||||
|
|
||||||
|
# headers = {
|
||||||
|
# "Authorization": f"Bearer {MP_ACCESS_TOKEN}",
|
||||||
|
# "Content-Type": "application/json",
|
||||||
|
# "X-Idempotency-Key": str(uuid.uuid4())
|
||||||
|
# }
|
||||||
|
|
||||||
|
# # MP Point API often prefers integer strings for CLP or exact strings
|
||||||
|
# # We use int() here if you are dealing with CLP (no cents)
|
||||||
|
# formatted_amount = str(int(float(total_amount)))
|
||||||
|
|
||||||
|
# payload = {
|
||||||
|
# "type": "point",
|
||||||
|
# "external_reference": f"ref_{int(time.time())}",
|
||||||
|
# "description": "Venta SekiPOS",
|
||||||
|
# "expiration_time": "PT16M",
|
||||||
|
# "transactions": {
|
||||||
|
# "payments": [
|
||||||
|
# {
|
||||||
|
# "amount": formatted_amount
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
# },
|
||||||
|
# "config": {
|
||||||
|
# "point": {
|
||||||
|
# "terminal_id": MP_TERMINAL_ID,
|
||||||
|
# "print_on_terminal": "no_ticket"
|
||||||
|
# },
|
||||||
|
# "payment_method": {
|
||||||
|
# "default_type": "credit_card"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# "integration_data": {
|
||||||
|
# "platform_id": "dev_1234567890",
|
||||||
|
# "integrator_id": "dev_1234567890"
|
||||||
|
# },
|
||||||
|
# "taxes": [
|
||||||
|
# {
|
||||||
|
# "payer_condition": "payment_taxable_iva"
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# # Verify the payload in your terminal if it fails again
|
||||||
|
# response = requests.post(url, json=payload, headers=headers)
|
||||||
|
|
||||||
|
# if response.status_code != 201 and response.status_code != 200:
|
||||||
|
# print(f"DEBUG MP ERROR: {response.text}")
|
||||||
|
|
||||||
|
# return jsonify(response.json()), response.status_code
|
||||||
|
# except Exception as e:
|
||||||
|
# return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
# @app.route('/api/mp-webhook', methods=['POST'])
|
||||||
|
# def webhook_notify():
|
||||||
|
# data = request.get_json()
|
||||||
|
# action = data.get('action', 'unknown')
|
||||||
|
# # Emitimos a todos los clientes conectados
|
||||||
|
# socketio.emit('payment_update', {
|
||||||
|
# "status": action,
|
||||||
|
# "id": data.get('data', {}).get('id')
|
||||||
|
# })
|
||||||
|
# return jsonify({"status": "ok"}), 200
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
init_db()
|
init_db()
|
||||||
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
|
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
|
||||||
|
|||||||
162
frutas2.json
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Paltas",
|
||||||
|
"plu": "3509",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3509-gem-avocado_1625011346.JPG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Papas",
|
||||||
|
"plu": "3414",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3414-baking-potato-white_1635179333.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Limones",
|
||||||
|
"plu": "3626",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3626-meyer-lemons_1460404763.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cebollines / Cebollas",
|
||||||
|
"plu": "4068",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4068-onions-green-2_1625063600.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ajo",
|
||||||
|
"plu": "4608",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zanahorias",
|
||||||
|
"plu": "4560",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Plátanos",
|
||||||
|
"plu": "4235",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4235-plantains-01_1625076376.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Manzanas",
|
||||||
|
"plu": "4099",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/apples-akane_1629314651.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Naranjas",
|
||||||
|
"plu": "4381",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4381-oranges-blood-01_1625082045.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Choclo",
|
||||||
|
"plu": "3087",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zapallo",
|
||||||
|
"plu": "4734",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4734-mini-pumpkin_1633964765.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zapallo italiano",
|
||||||
|
"plu": "4750",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4750-squash-acorn-01_1625751871.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Repollo",
|
||||||
|
"plu": "4069",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Betarragas",
|
||||||
|
"plu": "4537",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Porotos verdes",
|
||||||
|
"plu": "4527",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4527-beans-chinese-long-07_1625671743.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mandarinas",
|
||||||
|
"plu": "3524",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/noble-juicycrunch-productisolated-900x900-rgb-copy_1627662136.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Uvas",
|
||||||
|
"plu": "3491",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/arra-15-grape_1518122851.JPG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Duraznos",
|
||||||
|
"plu": "3113",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3113-peaches-donut-04_1614707155.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cerezas",
|
||||||
|
"plu": "3549",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/screenshot-2023-05-02-100428_1683036305.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peras",
|
||||||
|
"plu": "3317",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3317-angelys-pear_1460402454.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ciruelas",
|
||||||
|
"plu": "4435",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01_1625667846.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kiwi",
|
||||||
|
"plu": "3279",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3279-kiwi-gold-03_1614718637.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Piña",
|
||||||
|
"plu": "4430",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4430-pineapple-05_1625667649.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Puerros",
|
||||||
|
"plu": "4629",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Alcachofas",
|
||||||
|
"plu": "4519",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4519-artichokes-baby-02_1625671366.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Espárragos",
|
||||||
|
"plu": "4521",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01_1625671446.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Berenjena",
|
||||||
|
"plu": "4599",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Durazno Nectarin",
|
||||||
|
"plu": "3437",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/yellow-fleshed-flat-nectarine_1629140965.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Damascos",
|
||||||
|
"plu": "3044",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/3044-apricots-black-velvet-03_1614619576.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Membrillo",
|
||||||
|
"plu": "4447",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Champiñones",
|
||||||
|
"plu": "4647",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4647-mushrooms-chanterelles-1_1625681503.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Castañas",
|
||||||
|
"plu": "4927",
|
||||||
|
"image": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
4393
hid-scanner/build/gui_scanner/Analysis-00.toc
Normal file
2873
hid-scanner/build/gui_scanner/COLLECT-00.toc
Normal file
83
hid-scanner/build/gui_scanner/EXE-00.toc
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
('C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\gui_scanner.exe',
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\bootloader\\images\\icon-windowed.ico',
|
||||||
|
None,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
|
||||||
|
b'"urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo x'
|
||||||
|
b'mlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requested'
|
||||||
|
b'Privileges>\n <requestedExecutionLevel level="asInvoker" uiAccess='
|
||||||
|
b'"false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n '
|
||||||
|
b'<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\n <'
|
||||||
|
b'application>\n <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f'
|
||||||
|
b'0}"/>\n <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>\n '
|
||||||
|
b' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>\n <s'
|
||||||
|
b'upportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>\n <supporte'
|
||||||
|
b'dOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>\n </application>\n <'
|
||||||
|
b'/compatibility>\n <application xmlns="urn:schemas-microsoft-com:asm.v3">'
|
||||||
|
b'\n <windowsSettings>\n <longPathAware xmlns="http://schemas.micros'
|
||||||
|
b'oft.com/SMI/2016/WindowsSettings">true</longPathAware>\n </windowsSett'
|
||||||
|
b'ings>\n </application>\n <dependency>\n <dependentAssembly>\n <ass'
|
||||||
|
b'emblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version='
|
||||||
|
b'"6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" langua'
|
||||||
|
b'ge="*"/>\n </dependentAssembly>\n </dependency>\n</assembly>',
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\gui_scanner.pkg',
|
||||||
|
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||||
|
('PYZ-00.pyz',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\PYZ-00.pyz',
|
||||||
|
'PYZ'),
|
||||||
|
('struct',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\struct.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod01_archive',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod01_archive.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod02_importers',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod02_importers.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod03_ctypes',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod03_ctypes.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod04_pywin32',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod04_pywin32.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyiboot01_bootstrap',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_inspect',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_usb',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_usb.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_pkgutil',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_multiprocessing',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_setuptools',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_setuptools.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth__tkinter',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('gui_scanner',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\gui_scanner.py',
|
||||||
|
'PYSOURCE')],
|
||||||
|
[],
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
1773774112,
|
||||||
|
[('runw.exe',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\runw.exe',
|
||||||
|
'EXECUTABLE')],
|
||||||
|
'C:\\Python314\\python314.dll')
|
||||||
61
hid-scanner/build/gui_scanner/PKG-00.toc
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
('C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\gui_scanner.pkg',
|
||||||
|
{'BINARY': True,
|
||||||
|
'DATA': True,
|
||||||
|
'EXECUTABLE': True,
|
||||||
|
'EXTENSION': True,
|
||||||
|
'PYMODULE': True,
|
||||||
|
'PYSOURCE': True,
|
||||||
|
'PYZ': False,
|
||||||
|
'SPLASH': True,
|
||||||
|
'SYMLINK': False},
|
||||||
|
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||||
|
('PYZ-00.pyz',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\PYZ-00.pyz',
|
||||||
|
'PYZ'),
|
||||||
|
('struct',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\struct.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod01_archive',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod01_archive.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod02_importers',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod02_importers.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod03_ctypes',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod03_ctypes.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyimod04_pywin32',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\build\\gui_scanner\\localpycs\\pyimod04_pywin32.pyc',
|
||||||
|
'PYMODULE'),
|
||||||
|
('pyiboot01_bootstrap',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_inspect',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_usb',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\_pyinstaller_hooks_contrib\\rthooks\\pyi_rth_usb.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_pkgutil',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_multiprocessing',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth_setuptools',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_setuptools.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('pyi_rth__tkinter',
|
||||||
|
'C:\\Users\\Pepitho\\AppData\\Roaming\\Python\\Python314\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth__tkinter.py',
|
||||||
|
'PYSOURCE'),
|
||||||
|
('gui_scanner',
|
||||||
|
'C:\\Users\\Pepitho\\Desktop\\hid-scanner\\gui_scanner.py',
|
||||||
|
'PYSOURCE')],
|
||||||
|
'python314.dll',
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
[],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None)
|
||||||