34 Commits

Author SHA1 Message Date
55ff314163 Merge branch 'main' of https://gitea.sekidesu.xyz/SekiDesu01/SekiPOS 2026-03-13 18:07:54 -03:00
376b8c54a6 Version bump to v2.0, updated checkout and dicom 2026-03-13 18:07:09 -03:00
1a048a0e07 Fixed recepit lenght 2026-03-11 16:52:17 -03:00
77fc5920a2 image export like db 2026-03-11 15:59:50 -03:00
78d48db9ea added image_url column to dicom table 2026-03-10 23:39:44 -03:00
92e3a3f0f9 dicom evidence update 2026-03-10 23:22:50 -03:00
57cb27f6cf db dump tool 2026-03-10 22:47:40 -03:00
c24dae9694 actions button position fix 2026-03-10 22:41:34 -03:00
e0ac23a8e0 re-print old receipt from sales page 2026-03-10 22:37:47 -03:00
c1a06dc44c allow for inmediate confimation of checkout 2026-03-10 22:32:37 -03:00
72f6e0c822 receipt print fix 2026-03-10 22:25:09 -03:00
b2418d8c7e style fixes 2026-03-10 22:15:18 -03:00
9cb057668b login style fix 2026-03-10 21:41:15 -03:00
cb2aa89b16 WIP rewrite with macros 2026-03-10 20:15:58 -03:00
ef9a9296dd initial modularization + templates 2026-03-10 18:40:47 -03:00
3c4b2e148d Implemented Dicom 2026-03-09 18:25:49 -03:00
bf1bc84cd0 Checkout UI fixes 2026-03-09 16:54:55 -03:00
8e37f9e776 Implemented change calculator and various UI fixes 2026-03-09 16:32:23 -03:00
216abc8ad2 Fixed receipt design 2026-03-09 13:02:00 -03:00
cffa3d789b modified: templates/checkout.html 2026-03-09 11:34:49 -03:00
d7ef1573e5 modified: templates/checkout.html 2026-03-09 10:20:59 -03:00
e101833c7d modified: templates/checkout.html 2026-03-09 09:53:30 -03:00
6c98919c80 Added option forcustom product during checkout 2026-03-09 07:00:47 -03:00
cae35a266f Implemented receipt generation and printing 2026-03-09 06:20:24 -03:00
c57e8ab6db Implemented sales dashboard 2026-03-09 05:58:24 -03:00
135b14adcf Added persistent cart for chechout 2026-03-09 05:06:25 -03:00
9f59e122ef Various chechout improvements: subtotal rounding, placeholder color, and weight input now in grams for better UX. 2026-03-09 03:53:28 -03:00
43cc2a3caa Fixed UI inconsistencies and added logic to handle unit types in the product list and form. Also added a call to toggle the stock input visibility on page load based on the default unit type. 2026-03-09 02:55:07 -03:00
shironeko
2f2998b0fd esp attempt, stock + unit type 2026-03-07 19:21:14 -03:00
788b67804e oops 2026-03-07 03:35:22 -03:00
2bb38570f9 deactivated MP stuff 2026-03-07 03:23:00 -03:00
aacbce2557 oops 2026-03-07 03:07:55 -03:00
6c5085093d chekout update, mp study v1 2026-03-07 03:07:45 -03:00
423d563cc0 image compression, initial checkout(? 2026-03-04 22:47:02 -03:00
161 changed files with 4202 additions and 3881 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
pos_database.db
ScannerGO/ScannerGO-*
ScannerGO/config.json
DataToolsGO/imageTools-*
.env

11
.vscode/launch.json vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:pyenv"
}

15
.vscode/tasks.json vendored Normal file
View 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
View 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
View 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
View 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
View 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)
}
}

View File

@@ -10,6 +10,7 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY templates/ ./templates/
COPY static/ ./static/
#COPY .env .
# Create the folder structure for the volume mounts
RUN mkdir -p /app/static/cache

File diff suppressed because it is too large Load Diff

162
KeyGenerator/frutas2.json Normal file
View 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"
}
]

View File

@@ -11,7 +11,7 @@ from io import BytesIO
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# --- 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')
IMG_CACHE_DIR = os.path.join(os.getcwd(), 'image_cache')
os.makedirs(OUTPUT_DIR, exist_ok=True)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -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
View 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
View 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
View 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
}

View File

@@ -84,6 +84,7 @@ python app.py
## 📋 TODOs?
- Some form of user registration(?)
- Major refactoring of the codebase
## 🥼 Food Datasets
- https://www.ifpsglobal.com/plu-codes-search

5
SekiScaleESP/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
SekiScaleESP/.vscode/extensions.json vendored Normal file
View 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"
]
}

View 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
SekiScaleESP/lib/README Normal file
View 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

View 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:d1]
platform = espressif8266
board = d1
framework = arduino
monitor_speed = 115200
upload_port = /dev/ttyUSB1

39
SekiScaleESP/src/main.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include <Arduino.h>
const int triggerPin = D2;
bool lastState = HIGH;
void setup() {
Serial.begin(115200);
randomSeed(analogRead(0));
pinMode(LED_BUILTIN, OUTPUT);
pinMode(triggerPin, INPUT_PULLUP);
// Flash LED to signal boot
digitalWrite(LED_BUILTIN, LOW);
delay(500);
digitalWrite(LED_BUILTIN, HIGH);
}
void loop() {
bool currentState = digitalRead(triggerPin);
// Detect when D4 touches GND (Falling Edge)
if (currentState == LOW && lastState == HIGH) {
int randomWeight = random(100, 5000);
// Output formatted for easy parsing
Serial.print("WEIGHT:");
Serial.println(randomWeight);
// Visual feedback
digitalWrite(LED_BUILTIN, LOW);
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
delay(250); // Debounce
}
lastState = currentState;
}

11
SekiScaleESP/test/README Normal file
View 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

402
app.py
View File

@@ -1,12 +1,24 @@
import os
import sqlite3
import requests
from flask import send_file
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_from_directory
from flask_socketio import SocketIO, emit
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import mimetypes
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.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:
conn.execute('''CREATE TABLE IF NOT EXISTS users
(id INTEGER PRIMARY KEY, username TEXT UNIQUE, password TEXT)''')
# Updated table definition
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()
if not user:
hashed_pw = generate_password_hash('choripan1234')
@@ -105,23 +148,70 @@ def login():
user = conn.execute('SELECT * FROM users WHERE username = ?', (user_in,)).fetchone()
if user and check_password_hash(user[2], pass_in):
login_user(User(user[0], user[1]))
return redirect(url_for('index'))
return redirect(url_for('inventory'))
flash('Invalid credentials.')
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user(); return redirect(url_for('login'))
logout_user()
return redirect(url_for('login'))
@app.route('/')
@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:
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
def upsert():
d = request.form
@@ -129,18 +219,27 @@ def upsert():
try:
price = float(d['price'])
stock = float(d.get('stock', 0)) # New field
except (ValueError, TypeError):
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)
with sqlite3.connect(DB_FILE) as conn:
conn.execute('''INSERT INTO products (barcode, name, price, image_url) VALUES (?,?,?,?)
ON CONFLICT(barcode) DO UPDATE SET name=excluded.name,
price=excluded.price, image_url=excluded.image_url''',
(barcode, d['name'], price, final_image_path))
# Updated UPSERT query
conn.execute('''INSERT INTO products (barcode, name, price, image_url, stock, unit_type)
VALUES (?,?,?,?,?,?)
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()
return redirect(url_for('index'))
return redirect(url_for('inventory'))
@app.route('/delete/<barcode>', methods=['POST'])
@login_required
@@ -152,7 +251,7 @@ def delete(barcode):
img_p = os.path.join(CACHE_DIR, f"{barcode}.jpg")
if os.path.exists(img_p): os.remove(img_p)
socketio.emit('product_deleted', {"barcode": barcode})
return redirect(url_for('index'))
return redirect(url_for('inventory'))
@app.route('/scan', methods=['GET'])
def scan():
@@ -161,13 +260,13 @@ def scan():
return jsonify({"status": "error", "message": "empty barcode"}), 400
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:
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/'):
clean_path = image_path.split('?')[0].lstrip('/')
if not os.path.exists(clean_path):
@@ -182,7 +281,9 @@ def scan():
"barcode": barcode_val,
"name": name,
"price": int(price),
"image": image_path
"image": image_path,
"stock": stock,
"unit_type": unit_type
}
socketio.emit('new_scan', product_data)
@@ -266,13 +367,274 @@ def upload_image():
barcode = request.form['barcode']
if file.filename == '' or not barcode:
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)
file.save(filepath)
timestamp = int(time.time())
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__':
init_db()
socketio.run(app, host='0.0.0.0', port=5000, debug=True)

162
frutas2.json Normal file
View 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"
}
]

BIN
image_cache/3044.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
image_cache/3087.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
image_cache/3113.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

BIN
image_cache/3279.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
image_cache/3317.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
image_cache/3414.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
image_cache/3437.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
image_cache/3491.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
image_cache/3509.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
image_cache/3524.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
image_cache/3549.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
image_cache/3626.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
image_cache/4068.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

BIN
image_cache/4069.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
image_cache/4099.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
image_cache/4235.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

BIN
image_cache/4381.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

BIN
image_cache/4430.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Some files were not shown because too many files have changed in this diff Show More