fix submit fitur prediksi dan pengurangan stock
This commit is contained in:
parent
ac2bb063bd
commit
6ad544fb75
|
|
@ -8,6 +8,7 @@ import 'package:finalproject/screens/transaction/transaction_page.dart';
|
||||||
import 'package:finalproject/screens/products/product_list_page.dart';
|
import 'package:finalproject/screens/products/product_list_page.dart';
|
||||||
import 'package:finalproject/screens/reports/report_page.dart';
|
import 'package:finalproject/screens/reports/report_page.dart';
|
||||||
import 'package:finalproject/screens/settings/settings_page.dart';
|
import 'package:finalproject/screens/settings/settings_page.dart';
|
||||||
|
import 'package:finalproject/utils/route_observer.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
|
|
@ -22,7 +23,8 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'Prediksi Stok Bahan Kue',
|
title: 'Prediksi Stok Bahan Kue',
|
||||||
theme: AppTheme.lightTheme(),
|
theme: AppTheme.lightTheme(),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
initialRoute: '/login',
|
navigatorObservers: [routeObserver],
|
||||||
|
initialRoute: '/splash',
|
||||||
routes: {
|
routes: {
|
||||||
'/splash': (context) => const SplashScreen(),
|
'/splash': (context) => const SplashScreen(),
|
||||||
'/login': (context) => const LoginScreen(),
|
'/login': (context) => const LoginScreen(),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class PredictionScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _PredictionScreenState extends State<PredictionScreen> {
|
class _PredictionScreenState extends State<PredictionScreen> {
|
||||||
late final PredictionController _controller;
|
late final PredictionController _controller;
|
||||||
|
final TextEditingController _productionController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -32,9 +33,94 @@ class _PredictionScreenState extends State<PredictionScreen> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (result['status'] == 'success') {
|
if (result['status'] == 'success') {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
await showDialog<void>(
|
||||||
const SnackBar(content: Text('✅ Stok berhasil diperbarui')),
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.12),
|
||||||
|
blurRadius: 18,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF10B981).withOpacity(0.15),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: Color(0xFF10B981),
|
||||||
|
size: 36,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
const Text(
|
||||||
|
'Produksi Berhasil',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Color(0xFF1F2937),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
const Text(
|
||||||
|
'Stok bahan sudah diperbarui sesuai produksi.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
height: 1.4,
|
||||||
|
color: Color(0xFF6B7280),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFA89080),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Sukses',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await _controller.refreshStock();
|
||||||
|
_controller.reset();
|
||||||
|
_productionController.clear();
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(result['message'] ?? 'Gagal submit')),
|
SnackBar(content: Text(result['message'] ?? 'Gagal submit')),
|
||||||
|
|
@ -341,6 +427,7 @@ class _PredictionScreenState extends State<PredictionScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
TextField(
|
TextField(
|
||||||
|
controller: _productionController,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged:
|
onChanged:
|
||||||
_controller.setProductionQuantity,
|
_controller.setProductionQuantity,
|
||||||
|
|
@ -419,7 +506,10 @@ class _PredictionScreenState extends State<PredictionScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: _controller.reset,
|
onPressed: () {
|
||||||
|
_controller.reset();
|
||||||
|
_productionController.clear();
|
||||||
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
side: const BorderSide(
|
side: const BorderSide(
|
||||||
color: Color(0xFFE5E7EB),
|
color: Color(0xFFE5E7EB),
|
||||||
|
|
@ -1151,6 +1241,7 @@ class _PredictionScreenState extends State<PredictionScreen> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_productionController.dispose();
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:finalproject/models/product_model.dart';
|
import 'package:finalproject/models/product_model.dart';
|
||||||
import 'package:finalproject/theme/colors.dart';
|
import 'package:finalproject/theme/colors.dart';
|
||||||
import 'package:finalproject/theme/text_styles.dart';
|
import 'package:finalproject/theme/text_styles.dart';
|
||||||
|
import 'package:finalproject/utils/route_observer.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'product_list_controller.dart';
|
import 'product_list_controller.dart';
|
||||||
|
|
@ -12,7 +13,7 @@ class ProductListScreen extends StatefulWidget {
|
||||||
State<ProductListScreen> createState() => _ProductListScreenState();
|
State<ProductListScreen> createState() => _ProductListScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ProductListScreenState extends State<ProductListScreen> {
|
class _ProductListScreenState extends State<ProductListScreen> with RouteAware {
|
||||||
late final ProductListController _controller;
|
late final ProductListController _controller;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -30,6 +31,20 @@ class _ProductListScreenState extends State<ProductListScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final route = ModalRoute.of(context);
|
||||||
|
if (route != null) {
|
||||||
|
routeObserver.subscribe(this, route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPopNext() {
|
||||||
|
_controller.loadProducts();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
|
|
@ -546,6 +561,7 @@ class _ProductListScreenState extends State<ProductListScreen> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
routeObserver.unsubscribe(this);
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,16 +64,16 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Navigate ke Dashboard
|
// Navigate ke Login
|
||||||
Navigator.of(context).pushReplacementNamed('/dashboard');
|
Navigator.of(context).pushReplacementNamed('/login');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() => _statusMessage = 'Error: ${e.toString()}');
|
setState(() => _statusMessage = 'Error: ${e.toString()}');
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Navigate ke Dashboard even on error
|
// Navigate ke Login even on error
|
||||||
Navigator.of(context).pushReplacementNamed('/dashboard');
|
Navigator.of(context).pushReplacementNamed('/login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,8 +166,10 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
|
|
||||||
// App name with fade-in
|
// App name with fade-in
|
||||||
FadeTransition(
|
FadeTransition(
|
||||||
opacity: Tween<double>(begin: 0.0, end: 1.0)
|
opacity: Tween<double>(
|
||||||
.animate(
|
begin: 0.0,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(
|
||||||
CurvedAnimation(
|
CurvedAnimation(
|
||||||
parent: _fadeController,
|
parent: _fadeController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
|
|
@ -237,8 +239,10 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
ScaleTransition(
|
ScaleTransition(
|
||||||
scale: Tween<double>(begin: 0.4, end: 1.0)
|
scale: Tween<double>(
|
||||||
.animate(
|
begin: 0.4,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(
|
||||||
CurvedAnimation(
|
CurvedAnimation(
|
||||||
parent: _rotateController,
|
parent: _rotateController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
|
|
@ -255,8 +259,10 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
ScaleTransition(
|
ScaleTransition(
|
||||||
scale: Tween<double>(begin: 0.4, end: 1.0)
|
scale: Tween<double>(
|
||||||
.animate(
|
begin: 0.4,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(
|
||||||
CurvedAnimation(
|
CurvedAnimation(
|
||||||
parent: _rotateController,
|
parent: _rotateController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
|
|
@ -273,8 +279,10 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
ScaleTransition(
|
ScaleTransition(
|
||||||
scale: Tween<double>(begin: 0.4, end: 1.0)
|
scale: Tween<double>(
|
||||||
.animate(
|
begin: 0.4,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(
|
||||||
CurvedAnimation(
|
CurvedAnimation(
|
||||||
parent: _rotateController,
|
parent: _rotateController,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ class MLService {
|
||||||
// API URL - Change based on environment
|
// API URL - Change based on environment
|
||||||
// Untuk emulator Android: 10.0.2.2
|
// Untuk emulator Android: 10.0.2.2
|
||||||
// Untuk device fisik: 192.168.x.x atau 127.0.0.1 kalau local
|
// Untuk device fisik: 192.168.x.x atau 127.0.0.1 kalau local
|
||||||
static const String baseUrl = 'http://192.168.1.12:5000';
|
static const String baseUrl = 'http://10.103.65.129:5000';
|
||||||
|
|
||||||
static const int timeoutSeconds = 30;
|
static const int timeoutSeconds = 30;
|
||||||
|
|
||||||
|
|
@ -229,14 +229,23 @@ class MLService {
|
||||||
print('[consumeStock] Response status: ${response.statusCode}');
|
print('[consumeStock] Response status: ${response.statusCode}');
|
||||||
print('[consumeStock] Response body: ${response.body}');
|
print('[consumeStock] Response body: ${response.body}');
|
||||||
|
|
||||||
|
final parsedBody =
|
||||||
|
response.body.isNotEmpty ? jsonDecode(response.body) : null;
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return jsonDecode(response.body);
|
return parsedBody is Map<String, dynamic>
|
||||||
} else {
|
? parsedBody
|
||||||
|
: {'status': 'success'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedBody is Map<String, dynamic>) {
|
||||||
|
return parsedBody;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'Server error: ${response.statusCode}',
|
'message': 'Server error: ${response.statusCode}',
|
||||||
};
|
};
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Consume stock error: $e');
|
print('Consume stock error: $e');
|
||||||
return {'status': 'error', 'message': 'Connection error: $e'};
|
return {'status': 'error', 'message': 'Connection error: $e'};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final RouteObserver<ModalRoute<void>> routeObserver =
|
||||||
|
RouteObserver<ModalRoute<void>>();
|
||||||
|
|
@ -21,9 +21,6 @@ logger = logging.getLogger(__name__)
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
# Prevent overlapping stock consumption transactions
|
|
||||||
transaction_in_progress = False
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# DATABASE CONFIGURATION
|
# DATABASE CONFIGURATION
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -63,6 +60,13 @@ def get_product_name_column(connection) -> str:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
return 'name' if has_name else 'product_name'
|
return 'name' if has_name else 'product_name'
|
||||||
|
|
||||||
|
def has_product_unit_column(connection) -> bool:
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("SHOW COLUMNS FROM products LIKE 'unit'")
|
||||||
|
has_unit = cursor.fetchone() is not None
|
||||||
|
cursor.close()
|
||||||
|
return has_unit
|
||||||
|
|
||||||
def grams_to_kg_rounded(grams: float) -> float:
|
def grams_to_kg_rounded(grams: float) -> float:
|
||||||
"""
|
"""
|
||||||
Convert grams to kilograms with rounding rules:
|
Convert grams to kilograms with rounding rules:
|
||||||
|
|
@ -468,17 +472,14 @@ def consume_stock():
|
||||||
"allow_partial": false
|
"allow_partial": false
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
global transaction_in_progress
|
print("FLASK TERBARU AKTIF")
|
||||||
if transaction_in_progress:
|
print("REQUEST RECEIVED")
|
||||||
return jsonify({
|
connection = None
|
||||||
'status': 'error',
|
cursor = None
|
||||||
'message': 'Transaction already in progress'
|
started_transaction = False
|
||||||
}), 429
|
|
||||||
|
|
||||||
transaction_in_progress = True
|
|
||||||
try:
|
try:
|
||||||
data = request.json or {}
|
data = request.json or {}
|
||||||
logger.info(f"[stock/consume] Incoming payload: {data}")
|
print("REQUEST DATA:", data)
|
||||||
items = data.get('items', [])
|
items = data.get('items', [])
|
||||||
allow_partial = bool(data.get('allow_partial', False))
|
allow_partial = bool(data.get('allow_partial', False))
|
||||||
|
|
||||||
|
|
@ -493,10 +494,11 @@ def consume_stock():
|
||||||
return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500
|
return jsonify({'status': 'error', 'message': 'Database connection failed'}), 500
|
||||||
|
|
||||||
name_column = get_product_name_column(connection)
|
name_column = get_product_name_column(connection)
|
||||||
|
unit_column_exists = has_product_unit_column(connection)
|
||||||
history_enabled = table_exists(connection, 'stock_usage_history')
|
history_enabled = table_exists(connection, 'stock_usage_history')
|
||||||
|
|
||||||
cursor = connection.cursor(dictionary=True)
|
cursor = connection.cursor(dictionary=True)
|
||||||
connection.start_transaction()
|
started_transaction = True
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
valid_items = []
|
valid_items = []
|
||||||
|
|
@ -514,14 +516,19 @@ def consume_stock():
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if unit_column_exists:
|
||||||
|
select_fields = f"id, {name_column} as name, current_stock, unit"
|
||||||
|
else:
|
||||||
|
select_fields = f"id, {name_column} as name, current_stock"
|
||||||
|
|
||||||
if product_id:
|
if product_id:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
f"SELECT id, {name_column} as name, current_stock, unit FROM products WHERE id = %s",
|
f"SELECT {select_fields} FROM products WHERE id = %s",
|
||||||
(product_id,)
|
(product_id,)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
f"SELECT id, {name_column} as name, current_stock, unit FROM products WHERE {name_column} = %s",
|
f"SELECT {select_fields} FROM products WHERE {name_column} = %s",
|
||||||
(product_name,)
|
(product_name,)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -573,14 +580,13 @@ def consume_stock():
|
||||||
|
|
||||||
if errors and not allow_partial:
|
if errors and not allow_partial:
|
||||||
connection.rollback()
|
connection.rollback()
|
||||||
cursor.close()
|
|
||||||
connection.close()
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'error',
|
'status': 'error',
|
||||||
'message': 'Ada item gagal diproses',
|
'message': 'Ada item gagal diproses',
|
||||||
'errors': errors
|
'errors': errors
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
|
print("UPDATING STOCK")
|
||||||
results = []
|
results = []
|
||||||
for entry in valid_items:
|
for entry in valid_items:
|
||||||
product = entry['product']
|
product = entry['product']
|
||||||
|
|
@ -618,8 +624,6 @@ def consume_stock():
|
||||||
})
|
})
|
||||||
|
|
||||||
connection.commit()
|
connection.commit()
|
||||||
cursor.close()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'status': 'success',
|
'status': 'success',
|
||||||
|
|
@ -629,10 +633,25 @@ def consume_stock():
|
||||||
}), 200
|
}), 200
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if connection and started_transaction:
|
||||||
|
try:
|
||||||
|
connection.rollback()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("[stock/consume] Failed to rollback transaction")
|
||||||
logger.error(f"Consume stock error: {str(e)}")
|
logger.error(f"Consume stock error: {str(e)}")
|
||||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||||
finally:
|
finally:
|
||||||
transaction_in_progress = False
|
if cursor is not None:
|
||||||
|
try:
|
||||||
|
cursor.close()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("[stock/consume] Failed to close cursor")
|
||||||
|
if connection is not None:
|
||||||
|
try:
|
||||||
|
connection.close()
|
||||||
|
except Exception:
|
||||||
|
logger.exception("[stock/consume] Failed to close connection")
|
||||||
|
print("REQUEST FINISHED")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/transactions', methods=['POST'])
|
@app.route('/transactions', methods=['POST'])
|
||||||
|
|
@ -998,6 +1017,11 @@ def internal_error(error):
|
||||||
return jsonify({'status': 'error', 'message': 'Internal server error'}), 500
|
return jsonify({'status': 'error', 'message': 'Internal server error'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/test')
|
||||||
|
def test():
|
||||||
|
return {"message": "FLASK BARU AKTIF"}
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# MAIN
|
# MAIN
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue