pispas_order_kitchen/
main.rs1#![windows_subsystem = "windows"]
2use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
27use serde::{Deserialize, Serialize};
28use std::fs;
29use std::sync::Mutex;
30use easy_trace::prelude::error;
31
32const COMANDAS_FILE: &str = "comandas.json";
33
34#[derive(Clone, Serialize, Deserialize)]
35struct Pedido {
36 canal: String,
37 numero: String,
38 estado: String, }
40
41#[derive(Clone, Serialize, Deserialize)]
42struct PedidoRequest {
43 canal: String,
44 numero: String,
45 estado: Option<String>,
46}
47
48#[derive(Clone, Serialize, Deserialize)]
49struct ModoConfig {
50 modo: String,
51}
52
53struct AppState {
54 pedidos: Mutex<Vec<Pedido>>,
55 modo: Mutex<String>,
56}
57
58fn guardar_comandas(pedidos: &Vec<Pedido>) -> Result<(), std::io::Error> {
59 let json = serde_json::to_string_pretty(pedidos)?;
60 fs::write(COMANDAS_FILE, json)?;
61 Ok(())
62}
63
64fn cargar_comandas() -> Vec<Pedido> {
65 if let Ok(data) = fs::read_to_string(COMANDAS_FILE) {
66 if let Ok(pedidos) = serde_json::from_str::<Vec<Pedido>>(&data) {
67 return pedidos;
68 }
69 }
70 Vec::new()
71}
72
73#[post("/api/pedido")]
74async fn set_pedido(data: web::Data<AppState>, json: web::Json<PedidoRequest>) -> impl Responder {
75 let mut pedidos = data.pedidos.lock().unwrap();
76 let nuevo_pedido = Pedido {
77 canal: json.canal.clone(),
78 numero: json.numero.clone(),
79 estado: json.estado.clone().unwrap_or_else(|| "en_preparacion".to_string()),
80 };
81 pedidos.push(nuevo_pedido);
82
83 if let Err(e) = guardar_comandas(&pedidos) {
85 error!("Error guardando comandas: {}", e);
86 return HttpResponse::InternalServerError().body("Error guardando datos");
87 }
88
89 HttpResponse::Ok().body("Pedido creado")
90}
91
92#[get("/api/pedidos")]
93async fn get_pedidos(data: web::Data<AppState>) -> impl Responder {
94 let pedidos = data.pedidos.lock().unwrap();
95 let pedidos_activos: Vec<Pedido> = pedidos.iter()
97 .filter(|p| p.estado != "recogido")
98 .cloned()
99 .collect();
100 HttpResponse::Ok().json(pedidos_activos)
101}
102
103#[post("/api/preparado")]
104async fn marcar_preparado(data: web::Data<AppState>, json: web::Json<PedidoRequest>) -> impl Responder {
105 let mut pedidos = data.pedidos.lock().unwrap();
106
107 let mut encontrado = false;
108 for pedido in pedidos.iter_mut() {
109 if pedido.canal == json.canal && pedido.numero == json.numero && pedido.estado == "en_preparacion" {
110 pedido.estado = "listo".to_string();
111 encontrado = true;
112 break;
113 }
114 }
115
116 if !encontrado {
117 return HttpResponse::NotFound().body("Pedido no encontrado o ya procesado");
118 }
119
120 if let Err(e) = guardar_comandas(&pedidos) {
122 error!("Error guardando comandas: {}", e);
123 return HttpResponse::InternalServerError().body("Error guardando datos");
124 }
125
126 HttpResponse::Ok().body("Pedido marcado como preparado")
127}
128
129#[post("/api/archivar")]
130async fn archivar(data: web::Data<AppState>, json: web::Json<PedidoRequest>) -> impl Responder {
131 let mut pedidos = data.pedidos.lock().unwrap();
132
133 let mut encontrado = false;
134 for pedido in pedidos.iter_mut() {
135 if pedido.canal == json.canal && pedido.numero == json.numero && pedido.estado == "listo" {
136 pedido.estado = "recogido".to_string();
137 encontrado = true;
138 break;
139 }
140 }
141
142 if !encontrado {
143 return HttpResponse::NotFound().body("Pedido no encontrado o ya procesado");
144 }
145
146 if let Err(e) = guardar_comandas(&pedidos) {
148 error!("Error guardando comandas: {}", e);
149 return HttpResponse::InternalServerError().body("Error guardando datos");
150 }
151
152 HttpResponse::Ok().body("Pedido archivado")
153}
154
155#[post("/api/modo")]
156async fn set_modo(data: web::Data<AppState>, json: web::Json<ModoConfig>) -> impl Responder {
157 let mut modo = data.modo.lock().unwrap();
158 *modo = json.modo.clone();
159 HttpResponse::Ok()
160}
161
162#[get("/api/modo")]
163async fn get_modo(data: web::Data<AppState>) -> impl Responder {
164 let modo = data.modo.lock().unwrap();
165 HttpResponse::Ok().json(ModoConfig { modo: modo.clone() })
166}
167
168const FAVICON_ICO: &[u8] = include_bytes!("../static/favicon.ico");
169const LOGO_DARK_PNG: &[u8] = include_bytes!("../static/LOGO_DARK.png");
170const LOGO_WHITE_PNG: &[u8] = include_bytes!("../static/LOGO_WHITE.png");
171const INDEX_HTML: &str = include_str!("../static/index.html");
172
173#[get("/")]
174async fn index() -> impl Responder {
175 HttpResponse::Ok()
176 .content_type("text/html; charset=utf-8")
177 .body(INDEX_HTML)
178}
179
180const KITCHEN_HTML: &str = include_str!("../static/kitchen.html");
181
182#[get("/kitchen")]
183async fn cocina() -> impl Responder {
184 HttpResponse::Ok()
185 .content_type("text/html; charset=utf-8")
186 .body(KITCHEN_HTML)
187}
188pub const ORDER_KITCHEN_LOG: &str = "order-kitchen.log";
189
190#[actix_web::main]
191async fn main() -> std::io::Result<()> {
192 easy_trace::init(
193 sharing::paths::user_log_dir().to_str().unwrap_or_default(),
194 ORDER_KITCHEN_LOG,
195 );
196
197 fs::create_dir_all("static").ok();
198
199 let state = web::Data::new(AppState {
200 pedidos: Mutex::new(cargar_comandas()), modo: Mutex::new(String::from("light")),
202 });
203
204 HttpServer::new(move || {
205 App::new()
206 .app_data(state.clone())
207 .service(set_pedido)
208 .service(get_pedidos)
209 .service(marcar_preparado)
210 .service(archivar)
211 .service(set_modo)
212 .service(get_modo)
213 .service(index)
214 .service(cocina)
215 .route(
216 "/favicon.ico",
217 web::get().to(|| async {
218 HttpResponse::Ok()
219 .content_type("image/x-icon")
220 .body(FAVICON_ICO)
221 }),
222 )
223 .route(
224 "/LOGO_DARK.png",
225 web::get().to(|| async {
226 HttpResponse::Ok()
227 .content_type("image/png")
228 .body(LOGO_DARK_PNG)
229 }),
230 )
231 .route(
232 "/LOGO_WHITE.png",
233 web::get().to(|| async {
234 HttpResponse::Ok()
235 .content_type("image/png")
236 .body(LOGO_WHITE_PNG)
237 }),
238 )
239 .service(actix_files::Files::new("/static", "static").show_files_listing())
240 })
241 .bind(("0.0.0.0", 8000))?
242 .run()
243 .await
244}