add produce flows
This commit is contained in:
parent
3da48add7e
commit
70b798f65f
6 changed files with 192 additions and 48 deletions
25
lib/api.dart
25
lib/api.dart
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:cdb_ui/pages/supply.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
@ -249,7 +250,9 @@ class API {
|
||||||
}
|
}
|
||||||
|
|
||||||
// /flow/<id>/end
|
// /flow/<id>/end
|
||||||
Future<List<String>?> endFlow(String id, {Map<String, int>? produced}) async {
|
Future<List<String>?> endFlow(String id, {List<SupplyForm>? produced}) async {
|
||||||
|
// todo : update api for supplyform
|
||||||
|
|
||||||
var resp = jsonDecode(
|
var resp = jsonDecode(
|
||||||
await postRequest("$instance/$id/end", {"produced": produced}));
|
await postRequest("$instance/$id/end", {"produced": produced}));
|
||||||
|
|
||||||
|
@ -365,6 +368,12 @@ class Price {
|
||||||
currency = json["currency"];
|
currency = json["currency"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Price.fromString(String value) {
|
||||||
|
var priceSplit = value.split(" ");
|
||||||
|
this.value = double.parse(priceSplit[0]);
|
||||||
|
currency = priceSplit[1];
|
||||||
|
}
|
||||||
|
|
||||||
String format() {
|
String format() {
|
||||||
return "${value.toStringAsFixed(2)} $currency";
|
return "${value.toStringAsFixed(2)} $currency";
|
||||||
}
|
}
|
||||||
|
@ -394,6 +403,20 @@ class Transaction {
|
||||||
consumed = json["consumed"] != null ? ConsumeInfo(json["consumed"]) : null;
|
consumed = json["consumed"] != null ? ConsumeInfo(json["consumed"]) : null;
|
||||||
location = json["location"] != null ? Location(json["location"]) : null;
|
location = json["location"] != null ? Location(json["location"]) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Transaction.inMemory(String itemID, String variant, String price,
|
||||||
|
String? origin, Location? location, String? note) {
|
||||||
|
uuid = "";
|
||||||
|
item = itemID;
|
||||||
|
variant = variant;
|
||||||
|
this.price = Price.fromString(price);
|
||||||
|
origin = origin;
|
||||||
|
timestamp = 0;
|
||||||
|
consumed = null;
|
||||||
|
expired = false;
|
||||||
|
note = note;
|
||||||
|
location = location;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConsumeInfo {
|
class ConsumeInfo {
|
||||||
|
|
|
@ -85,7 +85,8 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
|
||||||
children: [
|
children: [
|
||||||
Text("ID: ${widget.flow.id}"),
|
Text("ID: ${widget.flow.id}"),
|
||||||
Text("Started since: ${tsFormat(widget.flow.started)}"),
|
Text("Started since: ${tsFormat(widget.flow.started)}"),
|
||||||
...widget.flow.input!.map((x) => Text("Input: $x")).toList(),
|
if (widget.flow.input != null)
|
||||||
|
...widget.flow.input!.map((x) => Text("Input: $x")).toList(),
|
||||||
if (widget.flow.done != null) ...[
|
if (widget.flow.done != null) ...[
|
||||||
Text("Ended: ${tsFormat(widget.flow.done!.ended)}"),
|
Text("Ended: ${tsFormat(widget.flow.done!.ended)}"),
|
||||||
if (widget.flow.done!.next != null)
|
if (widget.flow.done!.next != null)
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import 'dart:js_interop_unsafe';
|
||||||
|
|
||||||
import 'package:cdb_ui/api.dart' as API;
|
import 'package:cdb_ui/api.dart' as API;
|
||||||
import 'package:cdb_ui/api.dart';
|
import 'package:cdb_ui/api.dart';
|
||||||
|
import 'package:cdb_ui/pages/supply.dart';
|
||||||
import 'package:cdb_ui/pages/transaction.dart';
|
import 'package:cdb_ui/pages/transaction.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
@ -14,7 +17,30 @@ class EndFlowWithProduce extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
||||||
Map<String, int> produces = {};
|
List<SupplyForm> produces = [];
|
||||||
|
late Map<String, Location> locations;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
API.API().getLocations().then(
|
||||||
|
(value) {
|
||||||
|
setState(() {
|
||||||
|
locations = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void addProduced(SupplyForm t) {
|
||||||
|
setState(() {
|
||||||
|
produces.add(t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> addProduceButtons() {
|
List<Widget> addProduceButtons() {
|
||||||
List<Widget> ret = [];
|
List<Widget> ret = [];
|
||||||
|
@ -22,7 +48,21 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
||||||
for (var i in widget.info.produces!) {
|
for (var i in widget.info.produces!) {
|
||||||
ret.add(ElevatedButton(
|
ret.add(ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// todo : implement adding
|
var (itemID, variant) = API.itemVariant(i);
|
||||||
|
API.API().getItem(itemID).then((item) {
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return SupplyPage(
|
||||||
|
item,
|
||||||
|
refresh,
|
||||||
|
onlyVariants: [variant],
|
||||||
|
forcePrice: "0.00",
|
||||||
|
forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}",
|
||||||
|
onCreate: addProduced,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: Text("Produced $i")));
|
child: Text("Produced $i")));
|
||||||
}
|
}
|
||||||
|
@ -48,6 +88,14 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
||||||
...addProduceButtons(),
|
...addProduceButtons(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
// todo : add produced list
|
// todo : add produced list
|
||||||
|
...produces.map((x) {
|
||||||
|
return TransactionCard(
|
||||||
|
x.transaction(locations),
|
||||||
|
() {},
|
||||||
|
onLongPress: (x) {},
|
||||||
|
onTap: (x) {},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
|
|
|
@ -9,7 +9,7 @@ class AddNotePage extends StatelessWidget {
|
||||||
|
|
||||||
AddNotePage(this.flow, this.refresh, {super.key});
|
AddNotePage(this.flow, this.refresh, {super.key});
|
||||||
|
|
||||||
_submit(BuildContext context) {
|
void _submit(BuildContext context) {
|
||||||
API.API().addNoteToFlow(flow.id, _noteController.text).then((x) {
|
API.API().addNoteToFlow(flow.id, _noteController.text).then((x) {
|
||||||
refresh();
|
refresh();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -32,7 +32,7 @@ class AddNotePage extends StatelessWidget {
|
||||||
height: 14,
|
height: 14,
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _submit(context), child: const Text("Add Note"))
|
onPressed: () => _submit(context), child: const Text("Add Note"))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,14 +5,26 @@ import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
||||||
class SupplyPage extends StatefulWidget {
|
class SupplyPage extends StatefulWidget {
|
||||||
final Item item;
|
final Item item;
|
||||||
final Function refresh;
|
final Function refresh;
|
||||||
|
final List<String>? onlyVariants;
|
||||||
|
final String? forcePrice;
|
||||||
|
final String? forceOrigin;
|
||||||
|
|
||||||
const SupplyPage(this.item, this.refresh, {super.key});
|
// callback function for receiving a transaction without creating on the API
|
||||||
|
final Function(SupplyForm)? onCreate;
|
||||||
|
|
||||||
|
const SupplyPage(this.item, this.refresh,
|
||||||
|
{this.onlyVariants,
|
||||||
|
this.onCreate,
|
||||||
|
this.forceOrigin,
|
||||||
|
this.forcePrice,
|
||||||
|
super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SupplyPage> createState() => _SupplyPageState();
|
State<SupplyPage> createState() => _SupplyPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SupplyPageState extends State<SupplyPage> {
|
class _SupplyPageState extends State<SupplyPage> {
|
||||||
|
late List<String> availableVariants;
|
||||||
late String variant;
|
late String variant;
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
String _selectedOrigin = "";
|
String _selectedOrigin = "";
|
||||||
|
@ -23,8 +35,13 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
availableVariants =
|
||||||
|
widget.onlyVariants ?? widget.item.variants.keys.toList();
|
||||||
|
|
||||||
variant = widget.item.variants.keys.first;
|
variant = widget.item.variants.keys.first;
|
||||||
_priceController = TextEditingController(text: "");
|
|
||||||
|
_selectedOrigin = widget.forceOrigin ?? "";
|
||||||
|
_priceController = TextEditingController(text: widget.forcePrice ?? "");
|
||||||
_noteController = TextEditingController(text: "");
|
_noteController = TextEditingController(text: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +49,23 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
_formKey.currentState!.save();
|
_formKey.currentState!.save();
|
||||||
|
|
||||||
|
if (widget.onCreate != null) {
|
||||||
|
// todo : create shadow transaction
|
||||||
|
var t = SupplyForm(
|
||||||
|
itemID: widget.item.id,
|
||||||
|
variant: variant,
|
||||||
|
price: "${_priceController.text} €",
|
||||||
|
origin: _selectedOrigin,
|
||||||
|
location: _selectedLocation,
|
||||||
|
note: _noteController.text);
|
||||||
|
|
||||||
|
widget.onCreate!(t);
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
widget.refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
API()
|
API()
|
||||||
.supplyItem(widget.item.id, variant, "${_priceController.text} €",
|
.supplyItem(widget.item.id, variant, "${_priceController.text} €",
|
||||||
_selectedOrigin, _selectedLocation, _noteController.text)
|
_selectedOrigin, _selectedLocation, _noteController.text)
|
||||||
|
@ -78,10 +112,11 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
variant = value!;
|
variant = value!;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
items: widget.item.variants.entries.map((entry) {
|
items: availableVariants.map((entryKey) {
|
||||||
|
var entry = widget.item.variants[entryKey]!;
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: entry.key,
|
value: entryKey,
|
||||||
child: Text(entry.value.name),
|
child: Text(entry.name),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onSaved: (value) {
|
onSaved: (value) {
|
||||||
|
@ -92,43 +127,45 @@ class _SupplyPageState extends State<SupplyPage> {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Origin Field with Dropdown and Text Input
|
// Origin Field with Dropdown and Text Input
|
||||||
AutocompletedTextField(
|
if (widget.forceOrigin == null)
|
||||||
options: origins,
|
AutocompletedTextField(
|
||||||
getValue: () => _selectedOrigin,
|
options: origins,
|
||||||
onChanged: (value) {
|
getValue: () => _selectedOrigin,
|
||||||
_selectedOrigin = value;
|
onChanged: (value) {
|
||||||
},
|
_selectedOrigin = value;
|
||||||
onSelection: (String selection) async {
|
},
|
||||||
var price = _priceController.text.isEmpty
|
onSelection: (String selection) async {
|
||||||
? await API()
|
var price = _priceController.text.isEmpty
|
||||||
.getLatestPrice(widget.item.id, variant,
|
? await API()
|
||||||
origin: selection)
|
.getLatestPrice(widget.item.id, variant,
|
||||||
.then((x) => x.value.toStringAsFixed(2))
|
origin: selection)
|
||||||
: _priceController.text;
|
.then((x) => x.value.toStringAsFixed(2))
|
||||||
setState(() {
|
: _priceController.text;
|
||||||
_priceController.text = price;
|
setState(() {
|
||||||
_selectedOrigin = selection;
|
_priceController.text = price;
|
||||||
});
|
_selectedOrigin = selection;
|
||||||
},
|
});
|
||||||
label: "Origin"),
|
},
|
||||||
|
label: "Origin"),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Price Field
|
// Price Field
|
||||||
TextFormField(
|
if (widget.forcePrice == null)
|
||||||
decoration: const InputDecoration(labelText: 'Price'),
|
TextFormField(
|
||||||
keyboardType: TextInputType.number,
|
decoration: const InputDecoration(labelText: 'Price'),
|
||||||
controller: _priceController,
|
keyboardType: TextInputType.number,
|
||||||
validator: (value) {
|
controller: _priceController,
|
||||||
if (value == null || value.isEmpty) {
|
validator: (value) {
|
||||||
return 'Please enter a price';
|
if (value == null || value.isEmpty) {
|
||||||
}
|
return 'Please enter a price';
|
||||||
if (double.tryParse(value) == null) {
|
}
|
||||||
return 'Please enter a valid number';
|
if (double.tryParse(value) == null) {
|
||||||
}
|
return 'Please enter a valid number';
|
||||||
return null;
|
}
|
||||||
},
|
return null;
|
||||||
),
|
},
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
@ -253,3 +290,37 @@ class AutocompletedTextField extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SupplyForm {
|
||||||
|
final String itemID;
|
||||||
|
final String variant;
|
||||||
|
final String price;
|
||||||
|
final String? origin;
|
||||||
|
final String? location;
|
||||||
|
final String note;
|
||||||
|
|
||||||
|
factory SupplyForm.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SupplyForm(
|
||||||
|
itemID: json['item'],
|
||||||
|
variant: json['variant'],
|
||||||
|
price: json['price'],
|
||||||
|
origin: json['origin'],
|
||||||
|
location: json['location'],
|
||||||
|
note: json['note'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SupplyForm({
|
||||||
|
required this.itemID,
|
||||||
|
required this.variant,
|
||||||
|
required this.price,
|
||||||
|
required this.origin,
|
||||||
|
required this.location,
|
||||||
|
required this.note,
|
||||||
|
});
|
||||||
|
|
||||||
|
Transaction transaction(Map<String, Location> locations) {
|
||||||
|
return Transaction.inMemory(itemID, variant, price, origin,
|
||||||
|
location != null ? locations[location!] : null, note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -222,10 +222,11 @@ class TransactionCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
if (t.timestamp != 0)
|
||||||
tsFormat(t.timestamp),
|
Text(
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
tsFormat(t.timestamp),
|
||||||
),
|
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if ((t.note ?? "").isNotEmpty) ...[
|
if ((t.note ?? "").isNotEmpty) ...[
|
||||||
|
|
Loading…
Reference in a new issue