chekout update, mp study v1
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ pos_database.db
|
||||
ScannerGO/ScannerGO-*
|
||||
ScannerGO/config.json
|
||||
DataToolsGO/imageTools-*
|
||||
.env
|
||||
@@ -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
@@ -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
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
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
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
|
||||
}
|
||||
82
app.py
82
app.py
@@ -7,6 +7,13 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import mimetypes
|
||||
import time
|
||||
import uuid
|
||||
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
|
||||
@@ -281,6 +288,81 @@ def upload_image():
|
||||
timestamp = int(time.time())
|
||||
return jsonify({"status": "success", "image_url": f"/static/cache/{filename}?t={timestamp}"}), 200
|
||||
|
||||
# @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)
|
||||
|
||||
@@ -3,3 +3,4 @@ Flask-Login==0.6.3
|
||||
Flask-SocketIO==5.6.1
|
||||
requests==2.32.5
|
||||
eventlet==0.36.1
|
||||
python-dotenv==1.2.2
|
||||
@@ -173,10 +173,69 @@
|
||||
[data-theme="dark"] .modal-body {
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="modal fade" id="removeConfirmModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Quitar Producto</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
¿Estás seguro de que quieres quitar <strong id="removeItemName"></strong> del carrito?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button class="btn btn-danger-discord" id="btn-confirm-remove">Quitar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="clearCartModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Vaciar Carrito</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<i class="bi bi-cart-x text-danger" style="font-size: 3rem;"></i>
|
||||
<p class="mt-3">¿Seguro que quieres eliminar todos los productos del carrito?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal">No, volver</button>
|
||||
<button class="btn btn-danger-discord" onclick="executeClearCart()">Sí, vaciar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="successModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-success">
|
||||
<div class="modal-body text-center py-4">
|
||||
<i class="bi bi-check-circle-fill text-success" style="font-size: 4rem;"></i>
|
||||
<h4 class="mt-3">¡Venta Exitosa!</h4>
|
||||
<p class="text-muted">El carrito se ha procesado correctamente.</p>
|
||||
<button class="btn btn-accent px-5" data-bs-dismiss="modal">Listo</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="navbar navbar-expand-md sticky-top px-3 mb-3">
|
||||
<span class="navbar-brand">SekiPOS <small class="text-muted fw-normal"
|
||||
style="font-size:0.65rem;">Caja</small></span>
|
||||
@@ -222,6 +281,7 @@
|
||||
<table class="table mt-3" id="cart-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Código</th>
|
||||
<th>Producto</th>
|
||||
<th>Precio/U</th>
|
||||
<th>Cant/Peso</th>
|
||||
@@ -310,12 +370,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function addToCart(product, qty) {
|
||||
const subtotal = product.price * qty;
|
||||
cart.push({ ...product, qty, subtotal });
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function renderCart() {
|
||||
const tbody = document.getElementById('cart-items');
|
||||
tbody.innerHTML = '';
|
||||
@@ -340,16 +394,163 @@
|
||||
document.getElementById('grand-total').innerText = clp.format(total);
|
||||
}
|
||||
|
||||
// Updated removeItem with confirmation prompt
|
||||
let itemIndexToRemove = null;
|
||||
|
||||
function removeItem(idx, name) {
|
||||
if (confirm(`¿Quitar ${name} del carrito?`)) {
|
||||
cart.splice(idx, 1);
|
||||
itemIndexToRemove = idx;
|
||||
document.getElementById('removeItemName').innerText = name;
|
||||
const modal = new bootstrap.Modal(document.getElementById('removeConfirmModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Attach listener to the confirm button in the modal
|
||||
document.getElementById('btn-confirm-remove').addEventListener('click', () => {
|
||||
if (itemIndexToRemove !== null) {
|
||||
cart.splice(itemIndexToRemove, 1);
|
||||
renderCart();
|
||||
bootstrap.Modal.getInstance(document.getElementById('removeConfirmModal')).hide();
|
||||
itemIndexToRemove = null;
|
||||
}
|
||||
});
|
||||
|
||||
function clearCart() {
|
||||
if (cart.length === 0) return;
|
||||
const modal = new bootstrap.Modal(document.getElementById('clearCartModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
function executeClearCart() {
|
||||
cart = [];
|
||||
renderCart();
|
||||
bootstrap.Modal.getInstance(document.getElementById('clearCartModal')).hide();
|
||||
}
|
||||
|
||||
async function processSale() {
|
||||
if (cart.length === 0) return;
|
||||
|
||||
const total = cart.reduce((sum, item) => sum + item.subtotal, 0);
|
||||
|
||||
// Disable button to prevent spam
|
||||
const btn = document.querySelector('button[onclick="processSale()"]');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Procesando...';
|
||||
|
||||
try {
|
||||
alert("total: " total)
|
||||
// const response = await fetch('/process_payment', {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ total: total })
|
||||
// });
|
||||
|
||||
// const result = await response.json();
|
||||
|
||||
// if (response.ok) {
|
||||
// const modal = new bootstrap.Modal(document.getElementById('successModal'));
|
||||
// modal.show();
|
||||
// cart = [];
|
||||
// renderCart();
|
||||
// } else {
|
||||
// alert("Error en el pago: " + (result.message || "Error desconocido"));
|
||||
// }
|
||||
} catch (err) {
|
||||
alert("Error de conexión con el servidor.");
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-cash-coin"></i> COBRAR';
|
||||
}
|
||||
}
|
||||
|
||||
function confirmWeight() {
|
||||
const weightInput = document.getElementById('weight-input');
|
||||
const weight = parseFloat(weightInput.value);
|
||||
if (weight > 0) {
|
||||
// For weighted items, we usually append new entries or you can sum them.
|
||||
// Here we append to allow different weighings of the same product type.
|
||||
addToCart(pendingProduct, weight);
|
||||
bootstrap.Modal.getInstance('#weightModal').hide();
|
||||
weightInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function addToCart(product, qty) {
|
||||
// Check if product (unit-based only) already exists in cart
|
||||
const existingIndex = cart.findIndex(item => item.barcode === product.barcode && item.unit !== 'kg');
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
cart[existingIndex].qty += qty;
|
||||
cart[existingIndex].subtotal = cart[existingIndex].qty * cart[existingIndex].price;
|
||||
} else {
|
||||
const subtotal = product.price * qty;
|
||||
cart.push({ ...product, qty, subtotal });
|
||||
}
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function updateQty(index, delta) {
|
||||
if (cart[index].unit === 'kg') return; // Delta buttons disabled for weighted items
|
||||
|
||||
cart[index].qty += delta;
|
||||
if (cart[index].qty <= 0) {
|
||||
removeItem(index, cart[index].name);
|
||||
} else {
|
||||
cart[index].subtotal = cart[index].qty * cart[index].price;
|
||||
renderCart();
|
||||
}
|
||||
}
|
||||
|
||||
function clearCart() { cart = []; renderCart(); }
|
||||
function processSale() { alert("Venta procesada con éxito"); clearCart(); }
|
||||
function manualQty(index, val) {
|
||||
const newQty = parseFloat(val);
|
||||
if (isNaN(newQty) || newQty <= 0) return;
|
||||
|
||||
cart[index].qty = newQty;
|
||||
cart[index].subtotal = cart[index].qty * cart[index].price;
|
||||
renderCart();
|
||||
// Don't re-render immediately to avoid losing input focus while typing
|
||||
}
|
||||
|
||||
function renderCart() {
|
||||
const tbody = document.getElementById('cart-items');
|
||||
tbody.innerHTML = '';
|
||||
let total = 0;
|
||||
|
||||
cart.forEach((item, index) => {
|
||||
total += item.subtotal;
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Logic for quantity controls
|
||||
let qtyControls;
|
||||
if (item.unit === 'kg') {
|
||||
qtyControls = `<span>${item.qty.toFixed(3)} kg</span>`;
|
||||
} else {
|
||||
qtyControls = `
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, -1)">-</button>
|
||||
<input type="number" class="form-control form-control-sm text-center p-0"
|
||||
style="width: 50px;" value="${item.qty}"
|
||||
onchange="manualQty(${index}, this.value)"
|
||||
onblur="renderCart()">
|
||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, 1)">+</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="font-monospace small text-muted">${item.barcode}</td>
|
||||
<td>${item.name}</td>
|
||||
<td>${clp.format(item.price)}</td>
|
||||
<td>${qtyControls}</td>
|
||||
<td>${clp.format(item.subtotal)}</td>
|
||||
<td>
|
||||
<button class="btn-danger-discord btn-sm" onclick="removeItem(${index}, '${item.name}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
document.getElementById('grand-total').innerText = clp.format(total);
|
||||
}
|
||||
|
||||
function applyTheme(t) {
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
|
||||
Reference in New Issue
Block a user