• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

inventree / inventree-app / 12270399751

11 Dec 2024 05:40AM UTC coverage: 8.545% (-0.05%) from 8.597%
12270399751

Pull #572

github

web-flow
Merge cc0085e50 into 0ef72dc3d
Pull Request #572: Order updates

0 of 55 new or added lines in 6 files covered. (0.0%)

2 existing lines in 2 files now uncovered.

725 of 8484 relevant lines covered (8.55%)

0.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/widget/order/purchase_order_detail.dart
1
import "package:flutter/material.dart";
2
import "package:flutter_speed_dial/flutter_speed_dial.dart";
3
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
4

5
import "package:inventree/app_colors.dart";
6
import "package:inventree/barcode/barcode.dart";
7
import "package:inventree/barcode/purchase_order.dart";
8
import "package:inventree/helpers.dart";
9
import "package:inventree/l10.dart";
10

11
import "package:inventree/inventree/model.dart";
12
import "package:inventree/inventree/company.dart";
13
import "package:inventree/inventree/stock.dart";
14
import "package:inventree/inventree/purchase_order.dart";
15

16
import "package:inventree/widget/dialogs.dart";
17
import "package:inventree/widget/stock/location_display.dart";
18
import "package:inventree/widget/order/po_line_list.dart";
19

20

21
import "package:inventree/widget/attachment_widget.dart";
22
import "package:inventree/widget/company/company_detail.dart";
23
import "package:inventree/widget/notes_widget.dart";
24
import "package:inventree/widget/progress.dart";
25
import "package:inventree/widget/refreshable_state.dart";
26
import "package:inventree/widget/snacks.dart";
27
import "package:inventree/widget/stock/stock_list.dart";
28
import "package:inventree/preferences.dart";
29

30

31
/*
32
 * Widget for viewing a single PurchaseOrder instance
33
 */
34
class PurchaseOrderDetailWidget extends StatefulWidget {
35

36
  const PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key);
×
37

38
  final InvenTreePurchaseOrder order;
39

40
  @override
×
41
  _PurchaseOrderDetailState createState() => _PurchaseOrderDetailState();
×
42
}
43

44

45
class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidget> {
46

47
  _PurchaseOrderDetailState();
×
48
  
49
  List<InvenTreePOLineItem> lines = [];
50

51
  InvenTreeStockLocation? destination;
52

53
  int completedLines = 0;
54

55
  int attachmentCount = 0;
56

57
  bool showCameraShortcut = true;
58
  bool supportProjectCodes = false;
59

60
  @override
×
61
  String getAppBarTitle() {
NEW
62
    String title = L10().purchaseOrder;
×
63

NEW
64
    if (widget.order.reference.isNotEmpty) {
×
NEW
65
      title += " - ${widget.order.reference}";
×
66
    }
67

68
    return title;
69
  }
70

71
  @override
×
72
  List<Widget> appBarActions(BuildContext context) {
73
    List<Widget> actions = [];
×
74

75
    if (widget.order.canEdit) {
×
76
      actions.add(
×
77
        IconButton(
×
78
          icon: Icon(TablerIcons.edit),
×
79
          tooltip: L10().purchaseOrderEdit,
×
80
          onPressed: () {
×
81
            editOrder(context);
×
82
          }
83
        )
84
      );
85
    }
86

87
    return actions;
88
  }
89

90
  @override
×
91
  List<SpeedDialChild> actionButtons(BuildContext context) {
92
    List<SpeedDialChild> actions = [];
×
93

94
    if (showCameraShortcut && widget.order.canEdit) {
×
95
      actions.add(
×
96
          SpeedDialChild(
×
97
              child: Icon(TablerIcons.camera, color: Colors.blue),
×
98
              label: L10().takePicture,
×
99
              onTap: () async {
×
100
                _uploadImage(context);
×
101
              }
102
          )
103
      );
104
    }
105

106
    if (widget.order.canCreate) {
×
107
      if (widget.order.isPending) {
×
108

109
        actions.add(
×
110
          SpeedDialChild(
×
111
            child: Icon(TablerIcons.circle_plus, color: Colors.green),
×
112
            label: L10().lineItemAdd,
×
113
            onTap: () async {
×
114
              _addLineItem(context);
×
115
            }
116
          )
117
        );
118

119
        actions.add(
×
120
          SpeedDialChild(
×
121
            child: Icon(TablerIcons.send, color: Colors.blue),
×
122
            label: L10().issueOrder,
×
123
            onTap: () async {
×
124
              _issueOrder(context);
×
125
            }
126
          )
127
        );
128
      }
129

130
      if (widget.order.isOpen) {
×
131
        actions.add(
×
132
          SpeedDialChild(
×
133
            child: Icon(TablerIcons.circle_x, color: Colors.red),
×
134
            label: L10().cancelOrder,
×
135
            onTap: () async {
×
136
              _cancelOrder(context);
×
137
            }
138
          )
139
        );
140
      }
141
    }
142

143
    return actions;
144
  }
145

146
  /// Add a new line item to this order
147
  Future<void> _addLineItem(BuildContext context) async {
×
148

149
    var fields = InvenTreePOLineItem().formFields();
×
150

151
    // Update part field definition
152
    fields["part"]?["hidden"] = false;
×
153
    fields["part"]?["filters"] = {
×
154
      "supplier": widget.order.supplierId
×
155
    };
156

157
    fields["order"]?["value"] = widget.order.pk;
×
158

159
    InvenTreePOLineItem().createForm(
×
160
      context,
161
      L10().lineItemAdd,
×
162
      fields: fields,
163
      onSuccess: (data) async {
×
164
        refresh(context);
×
165
        showSnackIcon(L10().lineItemUpdated, success: true);
×
166
      }
167
    );
168
  }
169

170
  /// Upload an image against the current PurchaseOrder
171
  Future<void> _uploadImage(BuildContext context) async {
×
172

173
    InvenTreePurchaseOrderAttachment().uploadImage(
×
174
        widget.order.pk,
×
175
        prefix: widget.order.reference,
×
176
    ).then((result) => refresh(context));
×
177
  }
178

179
  /// Issue this order
180
  Future<void> _issueOrder(BuildContext context) async {
×
181

182
    confirmationDialog(
×
183
      L10().issueOrder, "",
×
184
      icon: TablerIcons.send,
185
      color: Colors.blue,
186
      acceptText: L10().issue,
×
187
      onAccept: () async {
×
188
        await widget.order.issueOrder().then((dynamic) {
×
189
          refresh(context);
×
190
        });
191
      }
192
    );
193
  }
194

195
  /// Cancel this order
196
  Future<void> _cancelOrder(BuildContext context) async {
×
197

198
    confirmationDialog(
×
199
      L10().cancelOrder, "",
×
200
      icon: TablerIcons.circle_x,
201
      color: Colors.red,
202
      acceptText: L10().cancel,
×
203
      onAccept: () async {
×
204
        await widget.order.cancelOrder().then((dynamic) {
×
205
          refresh(context);
×
206
        });
207
      }
208
    );
209
  }
210

211
  @override
×
212
  List<SpeedDialChild> barcodeButtons(BuildContext context) {
213
    List<SpeedDialChild> actions = [];
×
214

215
    if (api.supportsBarcodePOReceiveEndpoint && widget.order.isPlaced) {
×
216
      actions.add(
×
217
        SpeedDialChild(
×
218
          child: Icon(Icons.barcode_reader),
×
219
          label: L10().scanReceivedParts,
×
220
          onTap:() async {
×
221
            scanBarcode(
×
222
              context,
223
              handler: POReceiveBarcodeHandler(purchaseOrder: widget.order),
×
224
            ).then((value) {
×
225
              refresh(context);
×
226
            });
227
          },
228
        )
229
      );
230
    }
231

232
    if (widget.order.isPending && api.supportsBarcodePOAddLineEndpoint) {
×
233
      actions.add(
×
234
        SpeedDialChild(
×
235
          child: Icon(TablerIcons.circle_plus, color: COLOR_SUCCESS),
×
236
          label: L10().lineItemAdd,
×
237
          onTap: () async {
×
238
            scanBarcode(
×
239
              context,
240
              handler: POAllocateBarcodeHandler(purchaseOrder: widget.order),
×
241
            );
242
          }
243
        )
244
      );
245
    }
246

247
    return actions;
248
  }
249

250

251
  @override
×
252
  Future<void> request(BuildContext context) async {
253
    await widget.order.reload();
×
254

255
    await api.PurchaseOrderStatus.load();
×
256

257
    lines = await widget.order.getLineItems();
×
258

259
    showCameraShortcut = await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true);
×
260
    supportProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED");
×
261

262
    completedLines = 0;
×
263

264
    for (var line in lines) {
×
265
      if (line.isComplete) {
×
266
        completedLines += 1;
×
267
      }
268
    }
269

270
    InvenTreePurchaseOrderAttachment().countAttachments(widget.order.pk).then((int value) {
×
271
      if (mounted) {
×
272
        setState(() {
×
273
          attachmentCount = value;
×
274
        });
275
      }
276
    });
277

NEW
278
    if (api.supportsPurchaseOrderDestination && widget.order.destinationId > 0) {
×
NEW
279
      InvenTreeStockLocation().get(widget.order.destinationId).then((InvenTreeModel? loc) {
×
NEW
280
        if (mounted) {
×
NEW
281
          if (loc != null && loc is InvenTreeStockLocation) {
×
NEW
282
            setState(() {
×
NEW
283
              destination = loc;
×
284
            });
285
          } else {
NEW
286
            setState(() {
×
NEW
287
              destination = null;
×
288
            });
289
          }
290
        }
291
      });
292
    } else {
NEW
293
      if (mounted) {
×
NEW
294
        setState(() {
×
NEW
295
          destination = null;
×
296
        });
297
      }
298
    }
299
  }
300

301
  // Edit the currently displayed PurchaseOrder
302
  Future <void> editOrder(BuildContext context) async {
×
303
    var fields = widget.order.formFields();
×
304

305
    // Cannot edit supplier field from here
306
    fields.remove("supplier");
×
307

308
    // Contact model not supported by server
309
    if (!api.supportsContactModel) {
×
310
      fields.remove("contact");
×
311
    }
312

313
    // ProjectCode model not supported by server
314
    if (!supportProjectCodes) {
×
315
      fields.remove("project_code");
×
316
    }
317

318
    widget.order.editForm(
×
319
      context,
320
      L10().purchaseOrderEdit,
×
321
      fields: fields,
322
      onSuccess: (data) async {
×
323
        refresh(context);
×
324
        showSnackIcon(L10().purchaseOrderUpdated, success: true);
×
325
      }
326
    );
327
  }
328

329
  Widget headerTile(BuildContext context) {
×
330

331
    InvenTreeCompany? supplier = widget.order.supplier;
×
332

333
    return Card(
×
334
        child: ListTile(
×
335
          title: Text(widget.order.reference),
×
336
          subtitle: Text(widget.order.description),
×
337
          leading: supplier == null ? null : api.getThumbnail(supplier.thumbnail),
×
338
          trailing: Text(
×
339
            api.PurchaseOrderStatus.label(widget.order.status),
×
340
            style: TextStyle(
×
341
              color: api.PurchaseOrderStatus.color(widget.order.status)
×
342
            ),
343
          )
344
        )
345
    );
346

347
  }
348

349
  List<Widget> orderTiles(BuildContext context) {
×
350

351
    List<Widget> tiles = [];
×
352

353
    InvenTreeCompany? supplier = widget.order.supplier;
×
354

355
    tiles.add(headerTile(context));
×
356

357
    if (supportProjectCodes && widget.order.hasProjectCode) {
×
358
      tiles.add(ListTile(
×
359
        title: Text(L10().projectCode),
×
360
        subtitle: Text("${widget.order.projectCode} - ${widget.order.projectCodeDescription}"),
×
361
        leading: Icon(TablerIcons.list),
×
362
      ));
363
    }
364

365
    if (supplier != null) {
366
      tiles.add(ListTile(
×
367
        title: Text(L10().supplier),
×
368
        subtitle: Text(supplier.name),
×
369
        leading: Icon(TablerIcons.building, color: COLOR_ACTION),
×
370
        onTap: () {
×
371
          Navigator.push(
×
372
            context,
373
            MaterialPageRoute(
×
374
              builder: (context) => CompanyDetailWidget(supplier)
×
375
            )
376
          );
377
        },
378
      ));
379
    }
380

381
    if (widget.order.supplierReference.isNotEmpty) {
×
382
      tiles.add(ListTile(
×
383
        title: Text(L10().supplierReference),
×
384
        subtitle: Text(widget.order.supplierReference),
×
385
        leading: Icon(TablerIcons.hash),
×
386
      ));
387
    }
388

389
    // Order destination
NEW
390
    if (destination != null) {
×
NEW
391
      tiles.add(ListTile(
×
NEW
392
        title: Text(L10().destination),
×
NEW
393
        subtitle: Text(destination!.name),
×
NEW
394
        leading: Icon(TablerIcons.map_pin, color: COLOR_ACTION),
×
NEW
395
        onTap: () => {
×
NEW
396
          Navigator.push(
×
397
            context,
NEW
398
            MaterialPageRoute(
×
NEW
399
              builder: (context) => LocationDisplayWidget(destination)
×
400
            )
401
          )
402
        }
403
      ));
404
    }
405

UNCOV
406
    Color lineColor = completedLines < widget.order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS;
×
407

408
    tiles.add(ListTile(
×
409
      title: Text(L10().lineItems),
×
410
      subtitle: ProgressBar(
×
411
        completedLines.toDouble(),
×
412
        maximum: widget.order.lineItemCount.toDouble(),
×
413
      ),
414
      leading: Icon(TablerIcons.clipboard_check),
×
415
      trailing: Text("${completedLines} /  ${widget.order.lineItemCount}", style: TextStyle(color: lineColor)),
×
416
    ));
417

418
    tiles.add(ListTile(
×
419
      title: Text(L10().totalPrice),
×
420
      leading: Icon(TablerIcons.currency_dollar),
×
421
      trailing: Text(
×
422
        renderCurrency(widget.order.totalPrice, widget.order.totalPriceCurrency)
×
423
      ),
424
    ));
425

426
    if (widget.order.issueDate.isNotEmpty) {
×
427
      tiles.add(ListTile(
×
428
        title: Text(L10().issueDate),
×
429
        subtitle: Text(widget.order.issueDate),
×
430
        leading: Icon(TablerIcons.calendar),
×
431
      ));
432
    }
433

434
    if (widget.order.targetDate.isNotEmpty) {
×
435
      tiles.add(ListTile(
×
436
        title: Text(L10().targetDate),
×
437
        subtitle: Text(widget.order.targetDate),
×
438
        leading: Icon(TablerIcons.calendar),
×
439
      ));
440
    }
441

442
    // Notes tile
443
    tiles.add(
×
444
        ListTile(
×
445
          title: Text(L10().notes),
×
446
          leading: Icon(TablerIcons.note, color: COLOR_ACTION),
×
447
          onTap: () {
×
448
            Navigator.push(
×
449
              context,
450
              MaterialPageRoute(
×
451
                builder: (context) => NotesWidget(widget.order)
×
452
              )
453
            );
454
          },
455
        )
456
    );
457

458
    // Attachments
459
    tiles.add(
×
460
        ListTile(
×
461
          title: Text(L10().attachments),
×
462
          leading: Icon(TablerIcons.file, color: COLOR_ACTION),
×
463
          trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null,
×
464
          onTap: () {
×
465
            Navigator.push(
×
466
              context,
467
              MaterialPageRoute(
×
468
                builder: (context) => AttachmentWidget(
×
469
                    InvenTreePurchaseOrderAttachment(),
×
470
                    widget.order.pk,
×
471
                    widget.order.reference,
×
472
                    widget.order.canEdit
×
473
                )
474
              )
475
            );
476
          },
477
        )
478
    );
479

480
    return tiles;
481

482
  }
483

484
  @override
×
485
  List<Widget> getTabIcons(BuildContext context) {
486
    return [
×
487
      Tab(text: L10().details),
×
488
      Tab(text: L10().lineItems),
×
489
      Tab(text: L10().received)
×
490
    ];
491
  }
492
  
493
  @override
×
494
  List<Widget> getTabs(BuildContext context) {
495
    return [
×
496
      ListView(children: orderTiles(context)),
×
497
      PaginatedPOLineList({"order": widget.order.pk.toString()}),
×
498
      // ListView(children: lineTiles(context)),
499
      PaginatedStockItemList({"purchase_order": widget.order.pk.toString()}),
×
500
    ];
501
  }
502
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc