kendrickfff commited on
Commit
4d88f32
·
verified ·
1 Parent(s): c72a5e1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -78
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import gradio as gr
2
  import sqlite3
3
  import datetime
@@ -84,9 +85,8 @@ def db_query(query, params=()):
84
 
85
  # --- Business Logic Functions ---
86
 
87
- # **FIXED:** Helper to get product data as List of Lists (for DataFrame value)
88
  def get_products_for_display_lol():
89
- """Get product data as list of lists (data only) for DataFrame."""
90
  products = db_query("SELECT id, name, price, stock FROM products ORDER BY name")
91
  if not products:
92
  return []
@@ -94,18 +94,15 @@ def get_products_for_display_lol():
94
  data = [[p.get('id', ''), p.get('name', ''), p.get('price', 0.0), p.get('stock', 0)] for p in products]
95
  return data
96
 
97
- # **FIXED:** add_product now returns gr.update with list of lists for the DataFrame
98
  def add_product(product_id, name, price_str, stock_str):
99
  """Adds a product to the database."""
100
  status_msg = ""
101
  # Validate input first
102
  if not all([product_id, name, price_str, stock_str]):
103
  status_msg = "Error: All product fields must be filled."
104
- # Still return update for the table for consistency
105
  return status_msg, gr.update(value=get_products_for_display_lol())
106
 
107
  try:
108
- # Convert price and stock, handle potential errors
109
  price_float = float(price_str)
110
  stock_int = int(stock_str)
111
  except (ValueError, TypeError):
@@ -117,56 +114,47 @@ def add_product(product_id, name, price_str, stock_str):
117
  return status_msg, gr.update(value=get_products_for_display_lol())
118
 
119
  # Proceed with INSERT query
120
- # NOTE: This still only ADDS. To make it "Add/Update", more logic is needed.
121
  query = "INSERT INTO products (id, name, price, stock) VALUES (?, ?, ?, ?)"
122
  result = db_execute(query, (product_id, name, price_float, stock_int))
123
 
124
  if result is not None:
125
  status_msg = f"Product '{name}' added successfully."
126
  else:
127
- # Possibility: ID already exists (PRIMARY KEY constraint)
128
  check_id = db_query("SELECT id FROM products WHERE id = ?", (product_id,))
129
  if check_id:
130
  status_msg = f"Error: Product ID '{product_id}' already exists."
131
  else:
132
  status_msg = "Error: Failed to add product to the database."
133
 
134
- # Get the latest product list in list-of-lists format
135
  updated_product_list_data = get_products_for_display_lol()
136
-
137
  # Return status AND the update object for the DataFrame using list of lists
138
  return status_msg, gr.update(value=updated_product_list_data)
139
 
140
- # **FIXED:** Function to get choices as list[tuple] for INITIAL dropdown load
141
  def get_product_choices_initial():
142
  """Gets product choices as list[tuple] for initial dropdown load."""
143
  products = db_query("SELECT id, name, price, stock FROM products WHERE stock > 0 ORDER BY name")
144
  choices = []
145
  if products:
146
  try:
147
- # Format: [(display_name, value), ...]
148
  choices = [(f"{p.get('name', 'N/A')} (Stock: {p.get('stock', 0)}, Price: {p.get('price', 0):.2f})", p.get('id'))
149
  for p in products if isinstance(p, dict) and p.get('id')]
150
  except Exception as e:
151
  print(f"Error formatting initial choices: {e}")
152
  choices = []
153
- # print("DEBUG: Initial choices for dropdown:", choices) # Keep commented unless debugging
154
  return choices # Return the raw list[tuple]
155
 
156
- # **FIXED:** Function returns gr.update object for DYNAMIC dropdown updates
157
  def get_product_choices_for_update():
158
  """Gets product choices and returns a Gradio update object for the Dropdown."""
159
  choices = get_product_choices_initial() # Reuse the logic to get list[tuple]
160
- print("DEBUG: New choices for dropdown update:", choices) # Keep commented unless debugging
161
  # Return the update object with the new choices
162
  return gr.update(choices=choices)
163
 
164
 
165
  def get_product_details(product_id):
166
  """Get details for a single product."""
167
- # Ensure product_id is a string
168
  if not isinstance(product_id, str):
169
- # print(f"DEBUG: Invalid product_id type received: {type(product_id)}, value: {repr(product_id)}")
170
  return None
171
  result = db_query("SELECT id, name, price, stock FROM products WHERE id = ?", (product_id,))
172
  return result[0] if result else None
@@ -174,21 +162,21 @@ def get_product_details(product_id):
174
  def process_sale(cart_items, amount_paid_str):
175
  """Process the sale, save to DB, update stock."""
176
  if not cart_items:
177
- return "Cart is empty.", ""
178
 
179
  try:
180
  amount_paid = float(amount_paid_str)
181
  if amount_paid < 0: raise ValueError("Paid amount cannot be negative")
182
  except (ValueError, TypeError):
183
- return "Invalid amount paid.", ""
184
 
185
  total_amount = sum(item['subtotal'] for item in cart_items)
186
 
187
  if amount_paid < total_amount:
188
- return f"Amount paid ({amount_paid:,.2f}) is less than the total amount ({total_amount:,.2f}).", ""
189
 
190
  change = amount_paid - total_amount
191
- transaction_id = f"TRX-{uuid.uuid4().hex[:8].upper()}" # Unique ID
192
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
193
 
194
  # --- Save to Database (within a single DB transaction) ---
@@ -210,12 +198,8 @@ def process_sale(cart_items, amount_paid_str):
210
  items_data = []
211
  for item in cart_items:
212
  items_data.append((
213
- transaction_id,
214
- item['id'],
215
- item['nama'], # Use name/price stored in cart item
216
- item['harga'],
217
- item['jumlah'],
218
- item['subtotal']
219
  ))
220
  cursor.executemany(item_insert_query, items_data)
221
 
@@ -223,7 +207,6 @@ def process_sale(cart_items, amount_paid_str):
223
  update_stock_query = "UPDATE products SET stock = stock - ? WHERE id = ?"
224
  stock_updates = []
225
  for item in cart_items:
226
- # Re-validate stock before update
227
  current_stock_row = cursor.execute("SELECT stock FROM products WHERE id = ?", (item['id'],)).fetchone()
228
  if not current_stock_row or current_stock_row[0] < item['jumlah']:
229
  item_name_for_error = item.get('nama', 'Unknown Item')
@@ -249,16 +232,17 @@ def process_sale(cart_items, amount_paid_str):
249
  receipt += "------------------------\n"
250
  receipt += " Thank You! \n"
251
 
 
252
  return f"Transaction Successful! Change: {change:,.2f}", receipt
253
 
254
  except sqlite3.Error as e:
255
  conn.rollback()
256
  print(f"Database Error during transaction: {e}")
257
- return f"Error saving transaction: {e}", ""
258
  except ValueError as e: # Catch stock validation error
259
  conn.rollback()
260
  print(f"Validation Error during transaction: {e}")
261
- return f"Error: {e}", ""
262
  finally:
263
  conn.close()
264
 
@@ -267,79 +251,66 @@ def process_sale(cart_items, amount_paid_str):
267
 
268
  def add_item_to_cart(selected_product_id, quantity, current_cart):
269
  """Adds item to the shopping cart state."""
270
- # Check type, should be string from dropdown value
271
  if not isinstance(selected_product_id, str) or quantity <= 0:
272
- # print(f"DEBUG: Invalid input type: ID={type(selected_product_id)}, Qty={type(quantity)}") # Keep commented unless debugging
273
  gr.Warning("Invalid product selection or quantity.")
274
- return current_cart # No change if input invalid
275
 
276
- product = get_product_details(selected_product_id) # selected_product_id SHOULD be the string ID
277
  if not product:
278
  gr.Warning(f"Product with ID '{selected_product_id}' not found.")
279
  return current_cart
280
 
281
- # Check stock BEFORE adding/updating cart
282
  if quantity > product['stock']:
283
  gr.Warning(f"Insufficient stock for {product['name']} (available: {product['stock']}).")
284
  return current_cart
285
 
286
- # Check if product already in cart, update quantity if so
287
  found = False
288
  for item in current_cart:
289
  if item['id'] == product['id']:
290
  new_quantity = item['jumlah'] + quantity
291
- # Check combined quantity against stock
292
  if new_quantity > product['stock']:
293
  gr.Warning(f"Total quantity in cart ({new_quantity}) exceeds stock for {product['name']} ({product['stock']}).")
294
- return current_cart # Don't add if exceeds stock
295
  item['jumlah'] = new_quantity
296
  item['subtotal'] = item['harga'] * new_quantity
297
  found = True
298
  gr.Info(f"Quantity for {product['name']} updated to {new_quantity}.")
299
  break
300
 
301
- # If product not found, add new item
302
  if not found:
303
  new_item = {
304
- "id": product['id'],
305
- "nama": product['name'], # Store name and price at time of adding
306
- "harga": product['price'],
307
- "jumlah": quantity,
308
- "subtotal": product['price'] * quantity
309
  }
310
  current_cart.append(new_item)
311
  gr.Info(f"{quantity} x {product['name']} added to cart.")
312
 
313
- return current_cart # Return the updated cart state (list of dicts)
 
314
 
315
- # **FIXED:** display_cart_and_total returns list[list] for DataFrame value update
316
  def display_cart_and_total(cart_state):
317
  """Prepares cart data (list of lists) and total amount string."""
318
  if not cart_state:
319
- return [], "Total: 0.00" # Return empty list and zero total if cart is empty
320
 
321
- # Format for gr.DataFrame value (list of lists)
322
  display_data_lol = []
323
  total = 0.0
324
  for item in cart_state:
325
- # Ensure order matches headers: ["Product Name", "Quantity", "Unit Price", "Subtotal"]
326
  display_data_lol.append([
327
- item.get('nama', 'N/A'),
328
- item.get('jumlah', 0),
329
- item.get('harga', 0.0),
330
- item.get('subtotal', 0.0)
331
  ])
332
  total += item.get('subtotal', 0.0)
333
 
334
- # Return the list of lists data and the formatted total string
335
  return display_data_lol, f"Total: {total:,.2f}"
336
 
337
- # **FIXED:** reset_cashier_ui now calls the function returning the correct update object
338
  def reset_cashier_ui():
339
- """Resets the cashier UI components after a transaction."""
340
- # Return default values AND the update object for the dropdown
341
- # Order MUST match the outputs list in the .then() call
342
- return [], 1, "Total: 0.00", "", "", "", get_product_choices_for_update()
 
343
 
344
  # --- Build Gradio Interface ---
345
  init_db() # Ensure DB and tables are ready on app start
@@ -354,7 +325,7 @@ with gr.Blocks(title="Simple POS v1.0 (Gradio)", theme=gr.themes.Soft()) as demo
354
  with gr.TabItem("🛒 POS / Cashier"):
355
  with gr.Row():
356
  with gr.Column(scale=1):
357
- # **FIXED:** Initialize dropdown choices with the function returning list[tuple]
358
  product_dropdown = gr.Dropdown(
359
  label="Select Product",
360
  choices=get_product_choices_initial(), # Use initial choices function
@@ -365,21 +336,19 @@ with gr.Blocks(title="Simple POS v1.0 (Gradio)", theme=gr.themes.Soft()) as demo
365
 
366
  with gr.Column(scale=2):
367
  gr.Markdown("#### Shopping Cart")
368
- # **FIXED:** Define cart_display DataFrame with static headers
369
  cart_display = gr.DataFrame(
370
  headers=["Product Name", "Quantity", "Unit Price", "Subtotal"],
371
- value=[], # Start empty
372
- interactive=False,
373
- datatype=["str", "number", "number", "number"]
374
  )
375
  total_display = gr.Textbox("Total: 0.00", label="Total Amount", interactive=False)
376
 
377
  with gr.Row():
378
- # Use type="text" for more flexible float input
379
  payment_input = gr.Textbox(label="Amount Paid", interactive=True, type="text")
380
  process_payment_btn = gr.Button("💰 Process Payment", variant="primary")
381
 
382
  with gr.Row():
 
383
  status_transaksi = gr.Textbox(label="Status", interactive=False)
384
  receipt_display = gr.Textbox(label="Receipt", lines=10, interactive=False)
385
 
@@ -388,19 +357,17 @@ with gr.Blocks(title="Simple POS v1.0 (Gradio)", theme=gr.themes.Soft()) as demo
388
  with gr.Row():
389
  product_id_input = gr.Textbox(label="Product ID")
390
  product_name_input = gr.Textbox(label="Product Name")
391
- # Use Textbox for flexible price/stock input, convert in backend
392
  product_price_input = gr.Textbox(label="Selling Price")
393
  product_stock_input = gr.Textbox(label="Initial Stock")
394
  add_product_btn = gr.Button("Add Product", variant="primary")
395
  status_produk = gr.Textbox(label="Status", interactive=False)
396
  gr.Markdown("---")
397
  gr.Markdown("#### Available Products List")
398
- # **FIXED:** Define product_display DataFrame with static headers & initial list[list] value
399
  product_display = gr.DataFrame(
400
  headers=["ID", "Name", "Price", "Stock"],
401
  value=get_products_for_display_lol(), # Initial data uses list of lists
402
- interactive=False,
403
- datatype=["str", "str", "number", "number"]
404
  )
405
 
406
 
@@ -414,20 +381,21 @@ with gr.Blocks(title="Simple POS v1.0 (Gradio)", theme=gr.themes.Soft()) as demo
414
  ).then( # Update the cart display & total AFTER state is updated
415
  fn=display_cart_and_total,
416
  inputs=[cart_state],
417
- # **FIXED:** Map outputs (list[list], total_string) to component values
418
- outputs=[cart_display, total_display]
419
  )
420
 
421
  process_payment_btn.click(
422
  fn=process_sale,
423
  inputs=[cart_state, payment_input],
424
- outputs=[status_transaksi, receipt_display] # Update status and receipt
425
  ).then( # Reset UI after payment process finishes
426
- fn=reset_cashier_ui,
427
  inputs=[],
428
- # **FIXED:** Map reset outputs to corresponding components, including dropdown update object
429
- outputs=[cart_state, quantity_input, total_display, payment_input, status_transaksi, receipt_display, product_dropdown]
430
- ).then( # Also update the cart display to empty after state reset
 
 
431
  fn=display_cart_and_total,
432
  inputs=[cart_state],
433
  outputs=[cart_display, total_display] # Update cart display value
@@ -438,8 +406,7 @@ with gr.Blocks(title="Simple POS v1.0 (Gradio)", theme=gr.themes.Soft()) as demo
438
  add_product_btn.click(
439
  fn=add_product,
440
  inputs=[product_id_input, product_name_input, product_price_input, product_stock_input],
441
- # **FIXED:** Map add_product outputs (status, gr.update object for DataFrame)
442
- outputs=[status_produk, product_display]
443
  ).then( # Also update the product dropdown choices in the POS tab
444
  fn=get_product_choices_for_update, # Call function that returns gr.update object
445
  inputs=[],
@@ -451,5 +418,4 @@ if __name__ == "__main__":
451
  # Initial setup message
452
  print(f"Ensure the database file '{DB_FILE}' is writable in the current directory.")
453
  print("Launching Gradio application...")
454
- # demo.launch(share=True) # To create a temporary public link via Gradio relay
455
  demo.launch() # Run on localhost
 
1
+ # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import sqlite3
4
  import datetime
 
85
 
86
  # --- Business Logic Functions ---
87
 
 
88
  def get_products_for_display_lol():
89
+ """Get product data as a list of lists (data only) for DataFrame."""
90
  products = db_query("SELECT id, name, price, stock FROM products ORDER BY name")
91
  if not products:
92
  return []
 
94
  data = [[p.get('id', ''), p.get('name', ''), p.get('price', 0.0), p.get('stock', 0)] for p in products]
95
  return data
96
 
 
97
  def add_product(product_id, name, price_str, stock_str):
98
  """Adds a product to the database."""
99
  status_msg = ""
100
  # Validate input first
101
  if not all([product_id, name, price_str, stock_str]):
102
  status_msg = "Error: All product fields must be filled."
 
103
  return status_msg, gr.update(value=get_products_for_display_lol())
104
 
105
  try:
 
106
  price_float = float(price_str)
107
  stock_int = int(stock_str)
108
  except (ValueError, TypeError):
 
114
  return status_msg, gr.update(value=get_products_for_display_lol())
115
 
116
  # Proceed with INSERT query
 
117
  query = "INSERT INTO products (id, name, price, stock) VALUES (?, ?, ?, ?)"
118
  result = db_execute(query, (product_id, name, price_float, stock_int))
119
 
120
  if result is not None:
121
  status_msg = f"Product '{name}' added successfully."
122
  else:
 
123
  check_id = db_query("SELECT id FROM products WHERE id = ?", (product_id,))
124
  if check_id:
125
  status_msg = f"Error: Product ID '{product_id}' already exists."
126
  else:
127
  status_msg = "Error: Failed to add product to the database."
128
 
 
129
  updated_product_list_data = get_products_for_display_lol()
 
130
  # Return status AND the update object for the DataFrame using list of lists
131
  return status_msg, gr.update(value=updated_product_list_data)
132
 
133
+
134
  def get_product_choices_initial():
135
  """Gets product choices as list[tuple] for initial dropdown load."""
136
  products = db_query("SELECT id, name, price, stock FROM products WHERE stock > 0 ORDER BY name")
137
  choices = []
138
  if products:
139
  try:
 
140
  choices = [(f"{p.get('name', 'N/A')} (Stock: {p.get('stock', 0)}, Price: {p.get('price', 0):.2f})", p.get('id'))
141
  for p in products if isinstance(p, dict) and p.get('id')]
142
  except Exception as e:
143
  print(f"Error formatting initial choices: {e}")
144
  choices = []
 
145
  return choices # Return the raw list[tuple]
146
 
147
+
148
  def get_product_choices_for_update():
149
  """Gets product choices and returns a Gradio update object for the Dropdown."""
150
  choices = get_product_choices_initial() # Reuse the logic to get list[tuple]
 
151
  # Return the update object with the new choices
152
  return gr.update(choices=choices)
153
 
154
 
155
  def get_product_details(product_id):
156
  """Get details for a single product."""
 
157
  if not isinstance(product_id, str):
 
158
  return None
159
  result = db_query("SELECT id, name, price, stock FROM products WHERE id = ?", (product_id,))
160
  return result[0] if result else None
 
162
  def process_sale(cart_items, amount_paid_str):
163
  """Process the sale, save to DB, update stock."""
164
  if not cart_items:
165
+ return "Cart is empty.", "" # Return empty receipt string on error
166
 
167
  try:
168
  amount_paid = float(amount_paid_str)
169
  if amount_paid < 0: raise ValueError("Paid amount cannot be negative")
170
  except (ValueError, TypeError):
171
+ return "Invalid amount paid.", "" # Return empty receipt string on error
172
 
173
  total_amount = sum(item['subtotal'] for item in cart_items)
174
 
175
  if amount_paid < total_amount:
176
+ return f"Amount paid ({amount_paid:,.2f}) is less than the total amount ({total_amount:,.2f}).", "" # Return empty receipt string
177
 
178
  change = amount_paid - total_amount
179
+ transaction_id = f"TRX-{uuid.uuid4().hex[:8].upper()}"
180
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
181
 
182
  # --- Save to Database (within a single DB transaction) ---
 
198
  items_data = []
199
  for item in cart_items:
200
  items_data.append((
201
+ transaction_id, item['id'], item['nama'], item['harga'],
202
+ item['jumlah'], item['subtotal']
 
 
 
 
203
  ))
204
  cursor.executemany(item_insert_query, items_data)
205
 
 
207
  update_stock_query = "UPDATE products SET stock = stock - ? WHERE id = ?"
208
  stock_updates = []
209
  for item in cart_items:
 
210
  current_stock_row = cursor.execute("SELECT stock FROM products WHERE id = ?", (item['id'],)).fetchone()
211
  if not current_stock_row or current_stock_row[0] < item['jumlah']:
212
  item_name_for_error = item.get('nama', 'Unknown Item')
 
232
  receipt += "------------------------\n"
233
  receipt += " Thank You! \n"
234
 
235
+ # Return SUCCESS status and the generated receipt
236
  return f"Transaction Successful! Change: {change:,.2f}", receipt
237
 
238
  except sqlite3.Error as e:
239
  conn.rollback()
240
  print(f"Database Error during transaction: {e}")
241
+ return f"Error saving transaction: {e}", "" # Return empty receipt string on DB error
242
  except ValueError as e: # Catch stock validation error
243
  conn.rollback()
244
  print(f"Validation Error during transaction: {e}")
245
+ return f"Error: {e}", "" # Return empty receipt string on validation error
246
  finally:
247
  conn.close()
248
 
 
251
 
252
  def add_item_to_cart(selected_product_id, quantity, current_cart):
253
  """Adds item to the shopping cart state."""
 
254
  if not isinstance(selected_product_id, str) or quantity <= 0:
 
255
  gr.Warning("Invalid product selection or quantity.")
256
+ return current_cart
257
 
258
+ product = get_product_details(selected_product_id)
259
  if not product:
260
  gr.Warning(f"Product with ID '{selected_product_id}' not found.")
261
  return current_cart
262
 
 
263
  if quantity > product['stock']:
264
  gr.Warning(f"Insufficient stock for {product['name']} (available: {product['stock']}).")
265
  return current_cart
266
 
 
267
  found = False
268
  for item in current_cart:
269
  if item['id'] == product['id']:
270
  new_quantity = item['jumlah'] + quantity
 
271
  if new_quantity > product['stock']:
272
  gr.Warning(f"Total quantity in cart ({new_quantity}) exceeds stock for {product['name']} ({product['stock']}).")
273
+ return current_cart
274
  item['jumlah'] = new_quantity
275
  item['subtotal'] = item['harga'] * new_quantity
276
  found = True
277
  gr.Info(f"Quantity for {product['name']} updated to {new_quantity}.")
278
  break
279
 
 
280
  if not found:
281
  new_item = {
282
+ "id": product['id'], "nama": product['name'], "harga": product['price'],
283
+ "jumlah": quantity, "subtotal": product['price'] * quantity
 
 
 
284
  }
285
  current_cart.append(new_item)
286
  gr.Info(f"{quantity} x {product['name']} added to cart.")
287
 
288
+ return current_cart
289
+
290
 
 
291
  def display_cart_and_total(cart_state):
292
  """Prepares cart data (list of lists) and total amount string."""
293
  if not cart_state:
294
+ return [], "Total: 0.00"
295
 
 
296
  display_data_lol = []
297
  total = 0.0
298
  for item in cart_state:
 
299
  display_data_lol.append([
300
+ item.get('nama', 'N/A'), item.get('jumlah', 0),
301
+ item.get('harga', 0.0), item.get('subtotal', 0.0)
 
 
302
  ])
303
  total += item.get('subtotal', 0.0)
304
 
 
305
  return display_data_lol, f"Total: {total:,.2f}"
306
 
307
+ # **FIXED:** reset_cashier_ui only resets inputs/state for the next transaction
308
  def reset_cashier_ui():
309
+ """Resets inputs/state for the next cashier transaction, keeps status/receipt."""
310
+ # Return values for: cart_state, quantity_input, total_display, payment_input, product_dropdown update object
311
+ # NO LONGER returns empty strings for status and receipt
312
+ return [], 1, "Total: 0.00", "", get_product_choices_for_update()
313
+
314
 
315
  # --- Build Gradio Interface ---
316
  init_db() # Ensure DB and tables are ready on app start
 
325
  with gr.TabItem("🛒 POS / Cashier"):
326
  with gr.Row():
327
  with gr.Column(scale=1):
328
+ # Initialize dropdown choices using the initial function
329
  product_dropdown = gr.Dropdown(
330
  label="Select Product",
331
  choices=get_product_choices_initial(), # Use initial choices function
 
336
 
337
  with gr.Column(scale=2):
338
  gr.Markdown("#### Shopping Cart")
339
+ # Define cart_display DataFrame with static headers
340
  cart_display = gr.DataFrame(
341
  headers=["Product Name", "Quantity", "Unit Price", "Subtotal"],
342
+ value=[], interactive=False, datatype=["str", "number", "number", "number"]
 
 
343
  )
344
  total_display = gr.Textbox("Total: 0.00", label="Total Amount", interactive=False)
345
 
346
  with gr.Row():
 
347
  payment_input = gr.Textbox(label="Amount Paid", interactive=True, type="text")
348
  process_payment_btn = gr.Button("💰 Process Payment", variant="primary")
349
 
350
  with gr.Row():
351
+ # Define Status and Receipt textboxes
352
  status_transaksi = gr.Textbox(label="Status", interactive=False)
353
  receipt_display = gr.Textbox(label="Receipt", lines=10, interactive=False)
354
 
 
357
  with gr.Row():
358
  product_id_input = gr.Textbox(label="Product ID")
359
  product_name_input = gr.Textbox(label="Product Name")
 
360
  product_price_input = gr.Textbox(label="Selling Price")
361
  product_stock_input = gr.Textbox(label="Initial Stock")
362
  add_product_btn = gr.Button("Add Product", variant="primary")
363
  status_produk = gr.Textbox(label="Status", interactive=False)
364
  gr.Markdown("---")
365
  gr.Markdown("#### Available Products List")
366
+ # Define product_display DataFrame with static headers & initial list[list] value
367
  product_display = gr.DataFrame(
368
  headers=["ID", "Name", "Price", "Stock"],
369
  value=get_products_for_display_lol(), # Initial data uses list of lists
370
+ interactive=False, datatype=["str", "str", "number", "number"]
 
371
  )
372
 
373
 
 
381
  ).then( # Update the cart display & total AFTER state is updated
382
  fn=display_cart_and_total,
383
  inputs=[cart_state],
384
+ outputs=[cart_display, total_display] # Update cart display value and total
 
385
  )
386
 
387
  process_payment_btn.click(
388
  fn=process_sale,
389
  inputs=[cart_state, payment_input],
390
+ outputs=[status_transaksi, receipt_display] # Update status and receipt FIRST
391
  ).then( # Reset UI after payment process finishes
392
+ fn=reset_cashier_ui, # Calls the revised reset function
393
  inputs=[],
394
+ # **FIXED:** Map only the outputs returned by the new reset_cashier_ui
395
+ # Order must match its return: cart_state, quantity_input, total_display, payment_input, product_dropdown
396
+ outputs=[cart_state, quantity_input, total_display, payment_input, product_dropdown]
397
+ # status_transaksi and receipt_display are NOT included here, preserving their values
398
+ ).then( # Update cart display to empty after state reset (Keep this)
399
  fn=display_cart_and_total,
400
  inputs=[cart_state],
401
  outputs=[cart_display, total_display] # Update cart display value
 
406
  add_product_btn.click(
407
  fn=add_product,
408
  inputs=[product_id_input, product_name_input, product_price_input, product_stock_input],
409
+ outputs=[status_produk, product_display] # Update status and product list DataFrame
 
410
  ).then( # Also update the product dropdown choices in the POS tab
411
  fn=get_product_choices_for_update, # Call function that returns gr.update object
412
  inputs=[],
 
418
  # Initial setup message
419
  print(f"Ensure the database file '{DB_FILE}' is writable in the current directory.")
420
  print("Launching Gradio application...")
 
421
  demo.launch() # Run on localhost