Compare commits

...

789 commits

Author SHA1 Message Date
Oliver
ae457e8235
Tweak parameter display size (#759)
Some checks failed
CI / test (push) Failing after 7m16s
Android / build (push) Has been cancelled
iOS / build (push) Has been cancelled
- Increase size of displayed parameter value
2026-01-22 23:35:22 +11:00
Oliver
a1f0576671
Run dart-format on commit (#760) 2026-01-22 23:35:16 +11:00
Oliver
c5bf4be3d1
Support logical and custom status fields for models (#758)
* Support logical and custom status fields for models

* Update release notes
2026-01-22 23:35:09 +11:00
Enmanuel Sancho Quintanilla
772c88170e
Improve Sales Order UX: Stock Location, Customer Filtering & Enhanced List View (#756)
* feat(sales-order): Improve UX with location display, customer filtering, and enhanced list

- Show stock location when selecting items to allocate in sales orders
- Filter inactive customers from customer selection dropdown
- Display customer name and order total in sales order list view

These changes improve the day-to-day usability for warehouse and field staff
by providing more context in key workflows without requiring extra navigation.

* Format code
2026-01-22 08:37:42 +11:00
Oliver
97e4c98e46
Default filters (#755)
* Bug fix for filters

- Fix key shadowing

* Adjust default filters for stock list

* Adjust default filter values

* Add "active" filter for stock items

* Code formatting

* Add "default ordering" option

* Enable pathstring sorting

* dart format
2026-01-20 09:11:40 +11:00
Oliver
74f468dc1b
Default filters (#749)
* Bug fix for filters

- Fix key shadowing

* Adjust default filters for stock list

* Adjust default filter values

* Add "active" filter for stock items

* Code formatting
2026-01-15 13:20:24 +11:00
Oliver
9002fb78d3
New Crowdin updates (#748)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (German)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2026-01-15 09:57:18 +11:00
Oliver
80f62091b1
Bump version number (#747) 2026-01-14 22:12:04 +11:00
Oliver
a4631cda7a
List filtering fix (#746)
* Bug fix for API forms without URL

- Ensure submitted data is returned

* Translate search fields

* Update release notes

* Remove debug message

* dart format
2026-01-14 15:15:21 +11:00
Oliver
225c40f9a6
New Crowdin updates (#744)
* New translations app_en.arb (Dutch)

* New translations app_en.arb (German)
2026-01-10 09:23:00 +11:00
Oliver
bf19ace3e9
New Crowdin updates (#742)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Romanian)
2026-01-07 10:38:10 +11:00
Oliver
8c15bdafdf
New Crowdin updates (#740)
* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (German)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Romanian)
2025-12-19 09:52:38 +11:00
Oliver
571ff1880f
New translations app_en.arb (Chinese Simplified) (#739) 2025-12-06 11:24:17 +11:00
Oliver
864c3eea76
Parameters refactor (#738)
* refactor attachment code into its own file

* Add getters

* Remove custom models for each type of attachment

* Refactor existing widgets

* Fix double camera open bug

* Add check for modern parameter API

* Add generic parameter type

* Remove old code

* Remove dead code

* Refactor previous widget

* Remove unused imports

* Refactor common code

* format

* Update release notes

* Helper func to render parameters list tile

* Display parameters on part page

* parameters for company

* Supplier more model types:

- ManufacturerPart
- SupplierPart
- PurchaseOrder
- SalesOrder

* dart format

* Fix image prefix

* Remove unused import

* Adjust API version
2025-12-04 18:34:05 +11:00
Oliver
346b1a150f
Attachments refactor (#737)
* refactor attachment code into its own file

* Add getters

* Remove custom models for each type of attachment

* Refactor existing widgets

* Fix double camera open bug

* Remove dead code

* Remove unused imports

* Refactor common code

* format

* Update release notes
2025-11-28 23:53:10 +11:00
Oliver
bb10117f01
New Crowdin updates (#736)
* New translations app_en.arb (Czech)

* New translations app_en.arb (Russian)
2025-11-28 20:01:55 +11:00
Oliver
0f31638bdc
[CI] Build Fixes (#734)
* precache for ios build

* build android on push too

* try without cache
2025-11-24 09:10:59 +11:00
Oliver
7283c07b76
Form success fix (#733)
* Fix order of operations on form success

- Pop widget before running callback

* Update release notes
2025-11-23 09:18:49 +11:00
Oliver
6e02d1da97
New Crowdin updates (#731)
* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Romanian)
2025-11-23 09:08:14 +11:00
Alexander Leisentritt
3ee2192c92
Update barcode scan pause instruction text (#728) 2025-11-22 07:27:26 +11:00
Oliver
01ac0fc59e
New Crowdin updates (#727)
* New translations app_en.arb (German)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Greek)
2025-11-22 07:26:55 +11:00
Oliver
13d95dd1b1
Modern Label printing (#724)
* Basic widget

* Redirect label printing action

* Refactor label printing code

* Construct form elements

* Refactor to allow re-use of forms

* Basic rendering of label printing form

* Remove dead code

* Pass custom handler through to form context

* Refactoring API forms:

- Allow custom pk field name
- Add callback when values change

* linting

* Dynamically rebuild form

* Handle nested fields

* Handle label printing status

* Run dart format

* Update release notes

* Remove unused var

* Enable close icon

* Handle initial plugin default value

* Store default values:

- Selected template (per label type)
- Selected printing plugin

* Dart format

* Fix dart linting

* use setter

* Just use a public field
2025-11-22 07:26:37 +11:00
Oliver
e41842a31d
Barcode tweaks (#726)
* Barcode tweaks

- Disable autozoom
- Update release notes
- Update contributor credits

* format
2025-11-20 07:51:19 +11:00
Oliver
d039f3cfcf
New Crowdin updates (#722)
* New translations app_en.arb (German)

* New translations app_en.arb (German)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Swedish)
2025-11-20 06:48:19 +11:00
Alexander Leisentritt
a75245b183
Add zoom slider to barcode scanning (#725) 2025-11-20 06:44:02 +11:00
Oliver
8a4750d298
New Crowdin updates (#719)
* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2025-11-15 08:03:12 +11:00
Oliver
6707f89019
Display supplier part stock (#717)
* Display supplier part stock

* dart format

* Update release notes
2025-11-13 23:54:03 +11:00
Oliver
ed7d73b9c0
Default location (#715)
* Fix async loading of parent part

- Don't block render while fetching data

* Display default location on part detail page

* Use pathstring instead of name

* Update release notes

* Display in stock detail too

* dart format
2025-11-13 23:37:05 +11:00
Oliver
0eedf25d11
New Crowdin updates (#709)
* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (German)
2025-11-13 23:16:07 +11:00
Oliver
daf3bf8291
Update python version for CI (#714) 2025-11-13 23:01:24 +11:00
Oliver
ae1d8dd188
File upload fix (#712)
* Allow file upload with non-strict https

- Create custom client for uploads
- Observe the USE_STRICT_HTTPS setting

* Formatting

* Update release notes

* Fix await code
2025-11-04 12:32:38 +11:00
Oliver
2252dd2fd6
Thumbnail errors (#708)
* Extract error info from thumbnail errors

* Formatting
2025-11-03 22:10:00 +11:00
Oliver
490d008447
Fix URL for reporting an issue via GitHub (#706)
* Fix URL for reporting an issue via GitHub

* Format
2025-11-03 21:39:48 +11:00
Oliver
0fc80b1be3
Tweaks (#707)
* Remove debug msg

* Only request test templates for testable parts

* Format
2025-11-03 21:39:39 +11:00
Oliver
9083f19531
New Crowdin updates (#703)
* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Swedish)
2025-11-03 20:33:54 +11:00
Oliver
2f8f42822a
Possible fix for barcode scanner issues (#702)
* Possible fix for barcode scanner issues

Ref: https://github.com/juliansteenbakker/mobile_scanner/issues/1454#issuecomment-3329405748

* Bump release notes

* Bump app revision
2025-10-31 15:26:18 +11:00
Oliver
2b68c30568
New translations app_en.arb (Portuguese) (#701) 2025-10-29 15:03:59 +11:00
Oliver
4a094f4a77
New Crowdin updates (#699)
* New translations app_en.arb (Russian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2025-10-26 18:41:29 +11:00
Oliver
a078a9d126
Update release notes (#698) 2025-10-24 13:48:10 +11:00
Oliver
624655ec6b
SalesOrderShipment (#697)
* Add detail widget for SalesOrderShipment

* Support editing of shipment details

* Rearrange SalesOrderDetail page

* Add support for attachments against shipment model

* refactoring

* Add shipment details page

* Add user actions for shipments:

- Check / uncheck
- Take photo

* Placeholder action to send shipment

* Send shipment from app

* Display pending shipments on the home screen

* Improve rending for shipments list

* Add class definition for SalesOrderAllocation

* Display list of items allocated against SalesOrderShipment

* Bump release notse

* Click through to stock item

* Bump version number

* dart format

* cleanup

* Remove unused imports
2025-10-24 13:36:10 +11:00
Oliver
6b67cc9e50
New Crowdin updates (#696)
* New translations app_en.arb (Russian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Vietnamese)
2025-10-21 16:28:53 +11:00
Oliver
790abb4c7d
New Crowdin updates (#695)
* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Portuguese, Brazilian)
2025-10-11 12:07:58 +11:00
Oliver
1407d8bc37
Use fvm for building workflows (#694) 2025-09-28 16:47:39 +10:00
Oliver
bdc5573311
Re-order barcode scanning priority (#693)
* Re-order barcode scanning priority

- Closes https://github.com/inventree/inventree-app/issues/692

* dart format

* Try with removed line

* Try without pythonscript

* just pub get

* try with fvm

* Remove fvm

* set working dir

* try just pub get

* Install python first

* Updates

* Use fvm

* Adjust CI
2025-09-28 13:34:22 +10:00
Oliver
d237a0e076
New Crowdin updates (#690)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (German)

* New translations app_en.arb (Chinese Simplified)
2025-09-04 11:33:00 +10:00
Oliver
449f4a4ce5
New Crowdin updates (#689)
* New translations app_en.arb (Italian)

* New translations app_en.arb (Chinese Simplified)
2025-08-30 21:56:57 +10:00
Oliver
3739c88e93
New Crowdin updates (#688)
* New translations app_en.arb (Polish)

* New translations app_en.arb (German)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (French)

* New translations app_en.arb (Chinese Simplified)
2025-08-26 18:12:09 +10:00
Oliver
8efae776a6
Update version (#687) 2025-08-19 11:39:15 +10:00
Oliver
a0b8795b27
Allow purchase orders to be completed directly from the app (#686)
* Allow purchase orders to be completed directly from the app

* Code formatting
2025-08-19 11:37:37 +10:00
Oliver
7d32bd6d88
bug fix: PO Lines (#685)
* bug fix: PO Lines

- Correctly display part images for purchase order line items

* Refactor

* Code formatting
2025-08-19 11:37:27 +10:00
Oliver
28701359da
New Crowdin updates (#684)
* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Ukrainian)
2025-08-19 11:14:07 +10:00
Oliver
b7a9062e0b
New Crowdin updates (#683)
* New translations app_en.arb (Hungarian)

* New translations app_en.arb (German)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Czech)
2025-08-11 21:16:04 +10:00
Oliver
bda9a9befb
New Crowdin updates (#682)
* New translations app_en.arb (Polish)

* New translations app_en.arb (German)
2025-08-03 09:45:21 +10:00
Oliver
292e96b799
New Crowdin updates (#681)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (German)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Spanish)
2025-07-27 13:49:36 +10:00
Oliver
7ad10fea60
New translations app_en.arb (Italian) (#680) 2025-07-08 08:37:47 +10:00
Oliver
fe30b4ee16
Add icons for settings views (#679) 2025-07-05 09:59:31 +10:00
Oliver
a2e27e7a8a
New Crowdin updates (#678)
* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2025-07-05 09:27:37 +10:00
Oliver
2adf8e3430
Link icons (#677)
* Add LinkIcon component

* Visual UI updates

- Refactor some components
- Clearer text display
- Add obvious chevron icon when a "tile" will take the user somewhere else

* dart format

* Adjust release notes

* Add visual separator

* Cleanup unused imports
2025-07-04 21:16:04 +10:00
Oliver
c30f1a19d1
Update version info and release notes (#672) 2025-07-01 16:42:45 +10:00
Oliver
1374bb5db1
Change ios project settings (#671) 2025-07-01 16:35:54 +10:00
Ben Hagen
1b59837b3b
Use raw value of UTF-8 encoded barcodes (#670) 2025-07-01 16:34:17 +10:00
Oliver
5bd5d34b24
Tweak for ios build (#669) 2025-06-29 16:57:31 +10:00
Oliver
675f9ee1bb
New Crowdin updates (#667)
* New translations app_en.arb (Italian)

* New translations app_en.arb (Chinese Simplified)
2025-06-29 10:46:42 +10:00
Oliver
2bb89a96ce
New Crowdin updates (#666)
* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2025-06-25 18:49:02 +10:00
Ben Hagen
55d14e7457
Add screenshot to README.md (#665) 2025-06-25 00:26:42 +10:00
Ben Hagen
eb30bbb2fa
feat: add image cropping functionality with custom aspect ratios (#638)
* feat: add image cropping functionality with custom aspect ratios

* Update release notes
2025-06-24 23:17:36 +10:00
Oliver
29ccd4ebfa
Contributors update (#664)
* Update credits

* Tweak release notes
2025-06-24 19:47:28 +10:00
Ben Hagen
2619adc87b
feat: theme update (#645) 2025-06-24 19:27:49 +10:00
Ben Hagen
2568a299fc
Fix image url in BUILDING.md (#663) 2025-06-24 19:15:42 +10:00
Ben Hagen
4444884afa
Format Code and Add Format Checks to CI (#643)
* Remove unused lib/generated/i18n.dart

* Use `fvm dart format .`

* Add contributing guidelines

* Enforce dart format

* Add `dart format off` directive to generated files
2025-06-24 09:55:01 +10:00
Oliver
e9db6532e4
Notification frequency (#662)
* Adjust notification check frequency

- 60s vs 5s

* Bump release notes
2025-06-23 20:11:20 +10:00
Oliver
a18c5d8354
Bump version to 0.19.0 (#661) 2025-06-23 19:58:15 +10:00
Oliver
fff16fec76
New translations app_en.arb (French) (#660) 2025-06-23 19:42:31 +10:00
Ben Hagen
c4e33a4c1a
Use FVM in GitHub Actions and migrate to Flutter 3.32.4 (#641)
* feat: implement Flutter version management using FVM across CI workflows

* Flutter 3.29.3 + minor Package upgrades

* Replace deprecated `withOpacity`

* Upgrade major package versions without breaking changes.

* Disable unnecessary_async rule

Re-enable later

* New language version and automated fixes

- unnecessary_breaks
- unnecessary_underscore

* Update BUILDING.md to use fvm commands

* Add gitignore files for Android and iOS build artifacts

* Migrate iOS dependencies to Swift Package Manager

This is being done automatically by Flutter

* Flutter 3.32.4

* New sdk version

* docs: add IDE setup instructions and troubleshooting guide for FVM integration
2025-06-23 19:42:05 +10:00
Oliver
cf012b2531
New Crowdin updates (#659)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (French)
2025-06-21 10:29:17 +10:00
Oliver
d651506315
New Crowdin updates (#657)
* New translations app_en.arb (French)

* New translations app_en.arb (German)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2025-06-14 19:22:57 +10:00
Ben Hagen
d4ba866e10
Use ruff to format Python files (#658) 2025-06-14 19:21:05 +10:00
Oliver
13abcae84c
Part pricing detail (#655)
* Implement part pricing data and new part pricing widget

* improve part pricing widget and part pricing data. Add part pricing setting.

* Refactor helper func

* Tweak translated string

* Refactor part pricing page

* Update release notes

* Fixes

* More cleanup

---------

Co-authored-by: JarEXE <eykenj@gmail.com>
2025-06-14 10:56:56 +10:00
Oliver
13cb2f9164
Docs url fix (#654)
* Fix broken docs URL

* Update release noets

* const tweak
2025-06-14 09:16:32 +10:00
Oliver
03aba4b4bf
New Crowdin updates (#652)
* New translations app_en.arb (French)

* New translations app_en.arb (German)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Persian)
2025-06-14 08:54:46 +10:00
Oliver
1ab171f619
Barcode scan refactor (#651)
* Build file changes

Lots of changes required to meet compatibility of new mobile_scanner lib

* Increase build memory

* Refactor camera controller

- Use MobileScanner now
- Much better UX / feedback

* Cleanup

* Move test sheet file

* Update release notes

* Move actions to floating action buttons

* Tweak deps

* tweak github actions

* Updates
2025-06-06 14:54:03 +10:00
Oliver
c3f390eddc
Tweak tasks.py (#648) 2025-06-04 11:17:38 +10:00
Oliver
9ea7a2348b
Fix simple typo (#647) 2025-06-02 22:40:53 +10:00
Oliver
5017391933
New Crowdin updates (#636)
* New translations app_en.arb (Polish)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (German)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (French)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Italian)
2025-06-02 22:40:45 +10:00
Oliver
52879c0fc2
Bug fixes (#637)
* Fix formatting

* Handle case where server does not provide permissions information

* Enhanced URL checks

* Enhanced check

* Fix for error dialog

* Bump version information
2025-04-18 19:12:34 +10:00
Oliver
762d1ae156
New Crowdin updates (#635)
* New translations app_en.arb (Spanish)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Ukrainian)
2025-04-17 16:34:17 +10:00
Oliver
dd268f4f61
Fix release notes (#634) 2025-04-16 00:00:52 +10:00
Oliver
1d5377ea20
Scroll fix (#633)
* Fix scrolling behaviuor

* Debounce paginated search
2025-04-15 23:42:48 +10:00
Oliver
72a78291b2
Order extra lines (#632)
* Define classes for extra line item

* Display PO extra line items

- Also, some refactoring

* Support extra line items for sales order

* linting fixes

* Update release notes
2025-04-15 20:49:05 +10:00
Oliver
25d7ac9189
Start date (#631)
* Support "start_date" for PurchaseOrder and SalesOrder

* Update release notes

* Add form fields
2025-04-15 15:45:09 +10:00
Oliver
5ec86c4ade
Tweak required roles (#630)
- Ref: https://github.com/inventree/InvenTree/pull/9506
2025-04-15 15:45:03 +10:00
Oliver
e11382b3b4
New Crowdin updates (#629)
* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Vietnamese)
2025-04-15 08:07:38 +10:00
Oliver
cae79a3922
New Crowdin updates (#622)
* New translations app_en.arb (German)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (German)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Russian)
2025-04-08 18:32:00 +10:00
Oliver
fcb4370ce8
New translations app_en.arb (Japanese) (#621) 2025-02-27 18:58:43 +11:00
Oliver
dc314d1c26
Company split search (#620)
* Allow split search by company type

* Bump release notes
2025-02-26 22:36:29 +11:00
Oliver
efe8db138b
New Crowdin updates (#619)
* New translations app_en.arb (Italian)

* New translations app_en.arb (Italian)
2025-02-26 19:46:48 +11:00
Oliver
5672193ced
Tweak logic for creating new line items with barcode (#618) 2025-02-21 22:20:17 +11:00
Oliver
3c425de8f7
New translations app_en.arb (Hungarian) (#614) 2025-02-19 11:03:52 +11:00
Oliver
5e9ce7c0e7
New Crowdin updates (#611)
* New translations app_en.arb (French)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (German)

* New translations app_en.arb (German)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (German)
2025-02-13 09:00:54 +11:00
Oliver
1a3f48f48c
Company create (#610)
* API: refactor checkPermission

- Rename to checkRole (actually what it is doing)
- Permission check will be incoming

* Add checkPermission function for API

* Create a new company

* Bump release notes

* Cleanup

* Fix
2025-02-08 10:36:26 +11:00
Oliver
0c5944a8a0
New Crowdin updates (#607)
* New translations app_en.arb (Persian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Hungarian)
2025-02-08 09:52:20 +11:00
Oliver
0904d4e258
New Crowdin updates (#605)
* New translations app_en.arb (Arabic)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Romanian)
2025-01-27 13:56:35 +11:00
Oliver
d41d9b2a99
New Crowdin updates (#603)
* New translations app_en.arb (French)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)
2025-01-24 23:18:39 +11:00
Oliver
854ef95fbf
Order responsible (#602)
* Bump release notes and version

* Display responsible owner for purchase order

* Display responsible owner for sales order

* Display order completion date
2025-01-20 23:22:10 +11:00
Oliver
2ea29368ed
New translations app_en.arb (Romanian) (#601) 2025-01-19 20:29:01 +11:00
Oliver
0380b38311
Update version number and release notes (#600) 2025-01-13 20:21:06 +11:00
Oliver
60ec40e14c
New Crowdin updates (#599)
* New translations app_en.arb (French)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)
2025-01-13 20:08:42 +11:00
Oliver
1363f7ee3a
Edit attachment (#598)
* Edit attachment

- Allow editing of attachments once updloaed

* Update release notes

* Remove duplicate func
2025-01-11 17:11:17 +11:00
Oliver
94d43bc377
Tweak server profile edit window (#596)
* Tweak server profile edit window

* Fix typo
2025-01-11 16:40:17 +11:00
Oliver
8733deb46a
Dialog fixes (#597)
* Dialog fixes

- Fix bug which did not properly dismiss dialogs
- Closes https://github.com/inventree/inventree-app/issues/595

* Bump release notes
2025-01-11 16:40:07 +11:00
Oliver
543d041ca5
New Crowdin updates (#593)
* New translations app_en.arb (French)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (French)

* New translations app_en.arb (French)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Serbian (Latin))
2025-01-10 12:37:01 +11:00
Oliver
d97e4bc010
Remove out-dated permissions (#594)
* Remove out-dated permissions

* Fix github action
2025-01-10 12:24:33 +11:00
Oliver
562cbffb18
New Crowdin updates (#592)
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-12-24 10:58:21 +11:00
Oliver
f5e3b4ac80
Fix order of operations (#591) 2024-12-24 01:05:59 +11:00
Oliver
1a5992042a
New Crowdin updates (#589)
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-12-23 23:16:07 +11:00
Oliver
aac13ed5d6
Stock expiry (#590)
* Add stock expiry getters

* refactor date getters

* Display expiry information

* Update release notes

* Remove unused import
2024-12-23 22:25:55 +11:00
Oliver
d84f76d482
Improve error management for label printing (#588)
- Handle empty label
- Handle empty printer
2024-12-23 10:36:36 +11:00
Oliver
bc44b99d43
Label printing fix (#587)
* Handle blank URL provided for file download

* Improved printing checks

* Auto-select the correct printer
2024-12-23 09:57:13 +11:00
Oliver
dc8191c3d8
New translations app_en.arb (French) (#585) 2024-12-23 09:50:27 +11:00
Oliver
ea3410a3da
Sounds fix (#583)
* Prevent notification sounds from pausing external media

- Closes https://github.com/inventree/inventree-app/issues/582

* Bump release notes

* Bump version number

* Update release notes
2024-12-17 17:46:48 +11:00
Oliver
09625c81e2
New translations app_en.arb (Turkish) (#580) 2024-12-17 17:36:41 +11:00
Oliver
51fb889854
New Crowdin updates (#579)
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-12-15 08:55:32 +11:00
Oliver
665de2bd5a
Refactor search widget (#578)
* Refactor search widget

* Cleanup

* Fix for unallocated stock count

* Fix race condition

- Only upate results which match the current search term
- Prevents issues with multiple "competing" queries

* Fix for stock quantity

* Fix icon credits

* Tweak app bar color

* Cleanup visual stylinh
2024-12-14 17:29:35 +11:00
Oliver
524c5469f1
[refactor] Scan improvements (#577)
* Handle error on unexpected barcode response

* Add ManufacturerPart detail view

* Support barcode scanning for manufacturer part

* Refactoring for null checks

* Ignore selected errors in sentry

* Fix API implementation for ManufacturerPart

* Update release notes

* More error handling

* Decode quantity betterer

* Refactoring

* Add option to confirm checkin details

* Improve response handlign

* Cleanup

* Remove unused imports

* Fix async function

* Fix for assigning custom barcode

* Handle barcode scan result for company

* Fix

* Adjust scan priority

* Refactoring MODEL_TYPE

- Use instead of duplicated const strings

* @override fix
2024-12-14 15:24:23 +11:00
Oliver
6b179d108c
New Crowdin updates (#576)
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-12-12 10:16:43 +11:00
Oliver
e93e0241f6
Merge pull request #575 from inventree/receive-location
Receive location
2024-12-11 23:01:33 +11:00
Oliver Walters
6690f10118 Handle condition where camera controller fails to initialize 2024-12-11 22:53:03 +11:00
Oliver Walters
d25c47ccc3 Fix for unit test 2024-12-11 22:39:33 +11:00
Oliver Walters
e1912d6878 Tweak barcode overlay 2024-12-11 22:31:01 +11:00
Oliver Walters
9006fc382f Update release notes 2024-12-11 22:25:41 +11:00
Oliver Walters
676de0dfe9 Display part name 2024-12-11 22:23:52 +11:00
Oliver Walters
541eca387d Revert package change 2024-12-11 22:09:35 +11:00
Oliver Walters
2dd20d9565 Cleanup 2024-12-11 22:00:13 +11:00
Oliver Walters
5c1ceb7a0c Remove unused var 2024-12-11 21:55:31 +11:00
Oliver
35f3792d71
Merge pull request #573 from inventree/l10n_master
New Crowdin updates
2024-12-11 21:51:11 +11:00
Oliver Walters
2d9bc145bf Update release notes 2024-12-11 21:49:13 +11:00
Oliver Walters
e44d1ea5b4 Refactor loading overlay 2024-12-11 21:45:43 +11:00
Oliver Walters
d5a9f4310e Refactoring 2024-12-11 21:35:56 +11:00
Oliver Walters
19fdac46a7 Pre-fill the "location" field when receiving an item 2024-12-11 21:33:06 +11:00
Oliver
3025a3c280 New translations app_en.arb (Serbian (Latin)) 2024-12-11 19:24:55 +11:00
Oliver
d8bba1daba New translations app_en.arb (Hindi) 2024-12-11 19:24:54 +11:00
Oliver
fbe5ee2455 New translations app_en.arb (Latvian) 2024-12-11 19:24:53 +11:00
Oliver
0d10c0f539 New translations app_en.arb (Thai) 2024-12-11 19:24:52 +11:00
Oliver
50b7286be3 New translations app_en.arb (Spanish, Mexico) 2024-12-11 19:24:50 +11:00
Oliver
09dd7684f0 New translations app_en.arb (Portuguese, Brazilian) 2024-12-11 19:24:49 +11:00
Oliver
0bb7af7350 New translations app_en.arb (Vietnamese) 2024-12-11 19:24:48 +11:00
Oliver
1e4509854b New translations app_en.arb (Chinese Simplified) 2024-12-11 19:24:47 +11:00
Oliver
3bf9692c2a New translations app_en.arb (Ukrainian) 2024-12-11 19:24:45 +11:00
Oliver
3fe350a35e New translations app_en.arb (Swedish) 2024-12-11 19:24:44 +11:00
Oliver
5728da5361 New translations app_en.arb (Slovenian) 2024-12-11 19:24:43 +11:00
Oliver
5e7df6a3f0 New translations app_en.arb (Slovak) 2024-12-11 19:24:42 +11:00
Oliver
1ea11b5d7e New translations app_en.arb (Portuguese) 2024-12-11 19:24:40 +11:00
Oliver
32ac2e0b6b New translations app_en.arb (Norwegian) 2024-12-11 19:24:39 +11:00
Oliver
5281a1b6d2 New translations app_en.arb (Lithuanian) 2024-12-11 19:24:38 +11:00
Oliver
e3c24e76bf New translations app_en.arb (Korean) 2024-12-11 19:24:37 +11:00
Oliver
777a074331 New translations app_en.arb (Japanese) 2024-12-11 19:24:35 +11:00
Oliver
968dab0a96 New translations app_en.arb (Hungarian) 2024-12-11 19:24:34 +11:00
Oliver
345bb8be12 New translations app_en.arb (Finnish) 2024-12-11 19:24:33 +11:00
Oliver
3c5e884b38 New translations app_en.arb (German) 2024-12-11 19:24:31 +11:00
Oliver
7fa92fec17 New translations app_en.arb (Danish) 2024-12-11 19:24:30 +11:00
Oliver
626b66c055 New translations app_en.arb (Bulgarian) 2024-12-11 19:24:29 +11:00
Oliver
85265d781a New translations app_en.arb (Arabic) 2024-12-11 19:24:28 +11:00
Oliver
27022ac33c New translations app_en.arb (Romanian) 2024-12-11 19:24:27 +11:00
Oliver
4e73b4e43b New translations app_en.arb (Estonian) 2024-12-11 19:24:25 +11:00
Oliver
75fe2fe9f4 New translations app_en.arb (Persian) 2024-12-11 19:24:24 +11:00
Oliver
6ab241b0b5 New translations app_en.arb (Indonesian) 2024-12-11 19:24:23 +11:00
Oliver
940efa6aa9 New translations app_en.arb (Chinese Traditional) 2024-12-11 19:24:22 +11:00
Oliver
6aa2e3cdef New translations app_en.arb (Turkish) 2024-12-11 19:24:20 +11:00
Oliver
3937bb37e6 New translations app_en.arb (Russian) 2024-12-11 19:24:19 +11:00
Oliver
c2ef434988 New translations app_en.arb (Polish) 2024-12-11 19:24:18 +11:00
Oliver
5c85c98a0f New translations app_en.arb (Dutch) 2024-12-11 19:24:17 +11:00
Oliver
0b043dcaeb New translations app_en.arb (Hebrew) 2024-12-11 19:24:14 +11:00
Oliver
e0857b0462 New translations app_en.arb (Greek) 2024-12-11 19:24:13 +11:00
Oliver
a0c0e9dca4 New translations app_en.arb (Czech) 2024-12-11 19:24:12 +11:00
Oliver
0f4e1c8404 New translations app_en.arb (Spanish) 2024-12-11 19:24:11 +11:00
Oliver
18930e6201 New translations app_en.arb (French) 2024-12-11 19:24:09 +11:00
Oliver
3887458f0d New translations app_en.arb (Italian) 2024-12-11 19:24:08 +11:00
Oliver
9745bcfdb6
Merge pull request #572 from inventree/order-updates
Order updates
2024-12-11 16:44:36 +11:00
Oliver
cc0085e50f Adjust 2024-12-11 16:38:09 +11:00
Oliver
52a1eedc7e Update company display 2024-12-11 16:28:43 +11:00
Oliver
d016a663d8 Add order reference to app bar
(cherry picked from commit 260bfb56a225cb653d630a81098142b80569e302)
2024-12-11 16:07:41 +11:00
Oliver
2141c647c5 Display "destination" in purchase order line item detail
(cherry picked from commit 818f8cf7098df5dd83e86b9102288f22556c8700)
2024-12-11 16:07:35 +11:00
Oliver
e2a688315d Display "destination" in purchase order detail
(cherry picked from commit d0e4bf0246412938050f5de5a01b0b987cf9a0a2)
2024-12-11 16:07:28 +11:00
Oliver
08e01a729b Updates
(cherry picked from commit 94125667d9cdd1bd486aa58a2c4908c143b47225)
2024-12-11 16:07:22 +11:00
Oliver
0ef72dc3dd
Merge pull request #571 from inventree/barcode-scan-fix
Barcode scan fix
2024-12-11 15:18:42 +11:00
Oliver
8f802209a7 Update release notes 2024-12-11 13:41:12 +11:00
Oliver
a893d51e3c Cleanup 2024-12-11 13:40:39 +11:00
Oliver
ff82ed105d Fix recreation of scanned data
- Ensure RS and GS characters are correctly observed
2024-12-11 13:12:30 +11:00
Oliver
e95c35eebf
Merge pull request #569 from inventree/l10n_master
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Spanish)
2024-12-11 12:41:12 +11:00
Oliver
e70aa9c4fc New translations app_en.arb (Spanish) 2024-12-11 07:28:19 +11:00
Oliver
d5d1573eb3 New translations app_en.arb (Serbian (Latin)) 2024-12-10 19:33:46 +11:00
Oliver
3e1018c6ad New translations app_en.arb (Hindi) 2024-12-10 19:33:45 +11:00
Oliver
a08f64a2a2 New translations app_en.arb (Latvian) 2024-12-10 19:33:44 +11:00
Oliver
1e7232a688 New translations app_en.arb (Thai) 2024-12-10 19:33:43 +11:00
Oliver
42fc796019 New translations app_en.arb (Spanish, Mexico) 2024-12-10 19:33:42 +11:00
Oliver
493f83ef9a New translations app_en.arb (Portuguese, Brazilian) 2024-12-10 19:33:40 +11:00
Oliver
d9f08b3128 New translations app_en.arb (Vietnamese) 2024-12-10 19:33:39 +11:00
Oliver
9f392aaff7 New translations app_en.arb (Chinese Simplified) 2024-12-10 19:33:38 +11:00
Oliver
ac3841a102 New translations app_en.arb (Ukrainian) 2024-12-10 19:33:37 +11:00
Oliver
de45469954 New translations app_en.arb (Swedish) 2024-12-10 19:33:36 +11:00
Oliver
dd9f326dcb New translations app_en.arb (Slovenian) 2024-12-10 19:33:35 +11:00
Oliver
0f49298401 New translations app_en.arb (Slovak) 2024-12-10 19:33:33 +11:00
Oliver
58585cb213 New translations app_en.arb (Portuguese) 2024-12-10 19:33:32 +11:00
Oliver
b83ee998f0 New translations app_en.arb (Norwegian) 2024-12-10 19:33:31 +11:00
Oliver
6e0e099de8 New translations app_en.arb (Lithuanian) 2024-12-10 19:33:30 +11:00
Oliver
6f9995ed3a New translations app_en.arb (Korean) 2024-12-10 19:33:29 +11:00
Oliver
776f0e0ea5 New translations app_en.arb (Japanese) 2024-12-10 19:33:27 +11:00
Oliver
237d812e51 New translations app_en.arb (Hungarian) 2024-12-10 19:33:26 +11:00
Oliver
9420bf450a New translations app_en.arb (Finnish) 2024-12-10 19:33:25 +11:00
Oliver
73c4ef270e New translations app_en.arb (German) 2024-12-10 19:33:24 +11:00
Oliver
5589afaebf New translations app_en.arb (Danish) 2024-12-10 19:33:22 +11:00
Oliver
8348c55f63 New translations app_en.arb (Bulgarian) 2024-12-10 19:33:21 +11:00
Oliver
c9e0f90be9 New translations app_en.arb (Arabic) 2024-12-10 19:33:20 +11:00
Oliver
79618ba298 New translations app_en.arb (Romanian) 2024-12-10 19:33:19 +11:00
Oliver
8b5548f926 New translations app_en.arb (Estonian) 2024-12-10 19:33:18 +11:00
Oliver
ff40777d94 New translations app_en.arb (Persian) 2024-12-10 19:33:16 +11:00
Oliver
9d8668745c New translations app_en.arb (Indonesian) 2024-12-10 19:33:15 +11:00
Oliver
048c25267f New translations app_en.arb (Chinese Traditional) 2024-12-10 19:33:14 +11:00
Oliver
0c2957ae70 New translations app_en.arb (Turkish) 2024-12-10 19:33:13 +11:00
Oliver
ac56797498 New translations app_en.arb (Russian) 2024-12-10 19:33:12 +11:00
Oliver
66587e71ef New translations app_en.arb (Polish) 2024-12-10 19:33:11 +11:00
Oliver
6af819de0c New translations app_en.arb (Dutch) 2024-12-10 19:33:09 +11:00
Oliver
2658734a89 New translations app_en.arb (Hebrew) 2024-12-10 19:33:08 +11:00
Oliver
d008011fcc New translations app_en.arb (Greek) 2024-12-10 19:33:07 +11:00
Oliver
31dc193fa1 New translations app_en.arb (Czech) 2024-12-10 19:33:05 +11:00
Oliver
b25e763188 New translations app_en.arb (Spanish) 2024-12-10 19:33:04 +11:00
Oliver
fc26750641 New translations app_en.arb (French) 2024-12-10 19:33:03 +11:00
Oliver
d072c77218 New translations app_en.arb (Italian) 2024-12-10 19:33:02 +11:00
Oliver
f7d179e041
Assigned to me filter (#568)
* Add "assigned to me" order filter

* Update version and release notes
2024-12-10 19:30:02 +11:00
Oliver
3f7c2feeb7
New Crowdin updates (#567)
* New translations app_en.arb (Spanish)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Spanish, Mexico)
2024-12-09 23:06:04 +11:00
Oliver
5dab11ee0c
Bug fix for list filtering (#566)
- Observe "tristate" values
2024-12-06 12:51:02 +11:00
Oliver
707d251853
Screen wake (#565)
* Prevent screen turning off when scanning barcodes

- Close https://github.com/inventree/inventree-app/issues/492

* Update release notes
2024-12-06 11:11:47 +11:00
Oliver
6f5fc1d8a9
Test result fixes (#564)
* Fix association of test results to templates

* Fixes

* Remove unused vars
2024-12-06 10:59:49 +11:00
Oliver
b849bfc718
New Crowdin updates (#563)
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-12-06 10:31:57 +11:00
Oliver
d4cff1a5b9
Barcode scanner updates (#562)
* Add BUILDING.md

* Replace scaning library

- Out with qr_code_scanner
- In with flutter_zxing

* Update specs for jdk / kotlin / gradle

- NFI what this all means?

* Refactor barcode scanning widget

* Refactor barcode overlay

* Add handlers

* Update release notes

* Fix AppBar color

* Enhance attachment widget

* remove unused import

* Improved icon

* Select theme from main drawer
2024-12-06 00:08:04 +11:00
Oliver
4151aeb8e1
New Crowdin updates (#561)
* New translations app_en.arb (Italian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-12-05 19:54:30 +11:00
Oliver
2964950b26
Home screen (#559)
* Use grid view for home screen

* Update release notes

* Prune dead code
2024-12-05 17:10:56 +11:00
Oliver
d4b2204baf
Sales order shipment progress (#560)
- Display progress bar for sales order page
2024-12-05 17:09:57 +11:00
Oliver
9c12a83176
Specify app bar color (#558) 2024-12-05 14:55:10 +11:00
Oliver
2e798b1bd1
Order picture action (#557)
* Add "take picture" to purchase order detail

* Rename uploaded images

* Provide prefix when uploading images

* Add similar functionality for "sales order" detail

* Add new settings screens

* Control camera shortcut

* Bump release notes
2024-12-05 14:38:53 +11:00
Oliver
4698e7e82c
New translations app_en.arb (Polish) (#555) 2024-12-04 16:15:06 +11:00
Oliver
07a3f75981
New Crowdin updates (#554)
* New translations app_en.arb (Greek)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish)
2024-11-29 10:18:19 +11:00
Oliver
06bdd2d7f3
Update README.md (#553)
Add information on how to download / install
2024-11-24 14:37:28 +11:00
Oliver
a06ad4e804
New Crowdin updates (#551)
* New translations app_en.arb (Italian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Persian)
2024-11-22 11:27:19 +11:00
Oliver
20e454d287
StockItem Updates (#550)
* bump version

* Add new helpers for StockItem

* Navigate to sales order from stock item

* Navigate to customer (if specified)

* linting fix
2024-11-20 16:47:04 +11:00
Oliver
1a1521efe3
New Crowdin updates (#546)
* New translations app_en.arb (Polish)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Danish)
2024-11-20 12:11:56 +11:00
Oliver
4a2e91a371
New translations app_en.arb (Vietnamese) (#545) 2024-10-16 20:00:14 +11:00
Oliver
c0533db138
New Crowdin updates (#543)
* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Chinese Traditional)
2024-10-06 21:54:59 +11:00
Oliver
d990508237
Update Requirements (#541)
* Update package requiremenst

* github workflow updates

* ios build updates

* Theme adjustments

* Further updates

* Fix typo

* Deprecated imperative apply of Flutter's Gradle plugins

Ref: https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply

* Refactor wedge scanner

* Add context checks

* Adjust behaviour if testing

* Further refactoring

* Moar checks

* Logic fix

* Fix for wedge scanner test

* Fix for barcode processing

* Fix

* Yet another fix
2024-10-01 12:25:11 +10:00
Oliver
29948e5809
Version bump (#540)
* Update ios project details

* Update version number
2024-09-28 22:48:18 +10:00
Oliver
538a3d6ff6
Allow null values for decimal fields (#539)
* Allow null values for decimal fields

- fixes https://github.com/inventree/inventree-app/issues/538

* Update release notes

* Fix initial value
2024-09-27 23:08:46 +10:00
Oliver
ad48e5e172
New Crowdin updates (#536)
* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Indonesian)
2024-09-27 22:28:00 +10:00
Oliver
6c0b3cccc3
New Crowdin updates (#534)
* New translations app_en.arb (Lithuanian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (French)
2024-09-19 17:50:07 +10:00
Oliver
31cda0823a
Label fix (#533)
* Bug fix for stock item label printing

- Simple typo
- Closes https://github.com/inventree/inventree-app/issues/531

* Update version
2024-09-14 09:53:04 +10:00
Oliver
51ae10af7f
New Crowdin updates (#529)
* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Italian)
2024-09-14 09:52:56 +10:00
Oliver
23f27af4e6
Update CI workflow (#532) 2024-09-14 09:35:03 +10:00
Oliver
c52885fc6b
Attachments fix (#528)
* Bump version

* Fix for viewing and uploading attachment files

- Make sure we use the correct attribute!
2024-08-25 12:14:57 +10:00
Oliver
82aace9cc4
New Crowdin updates (#526)
* New translations app_en.arb (Arabic)

* New translations app_en.arb (Indonesian)
2024-08-25 12:08:56 +10:00
Oliver
9222439186
Update release notes (#523) 2024-08-23 08:13:22 +10:00
Oliver
7b60c857fd
New Crowdin updates (#522)
* New translations app_en.arb (Spanish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)
2024-08-23 08:10:30 +10:00
Andrew W
4558fff36c
Move function return to right line on scan success (#521)
Issue #520
2024-08-21 20:45:14 +10:00
Oliver
30cfcc5ffe
New Crowdin updates (#519)
* New translations app_en.arb (Swedish)

* New translations app_en.arb (Polish)
2024-08-18 16:13:08 +10:00
Oliver
f007a8da74
New Crowdin updates (#518)
* New translations app_en.arb (Swedish)

* New translations app_en.arb (Chinese Simplified)
2024-08-14 20:29:45 +10:00
Oliver
c9cad2f89f
Change from fontawesome to tabler icons (#516)
* Change from fontawesome to tabler icons

- Consistent with the frontend

* Cleanup conflicts

* Use double quotes

* remove unused import

* Update release notes

* Migrate some google icons to tabler icons

* Icon update

* Properly support display of custom icons

* Fix lookup
2024-08-08 19:44:44 +10:00
Oliver
42de3fd7d4
Order hold (#515)
* Add support for "ON_HOLD" status for orders

* Bump version and release notes

* Fix import
2024-08-07 21:11:40 +10:00
Oliver
693b4a4fce
New Crowdin updates (#514)
* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Thai)
2024-08-07 20:16:31 +10:00
Oliver
0485d5d089
New translations app_en.arb (Romanian) (#513) 2024-07-23 20:12:18 +10:00
Oliver
6ba4fa747e
Pin specific version of sentry_flutter (#512) 2024-07-16 12:32:47 +10:00
Oliver
8ba10f2578
Revert sentry version (#511)
- Caused issues with iOS app build
2024-07-15 21:52:28 +10:00
Oliver
856cf9eee4
New translations app_en.arb (Estonian) (#510) 2024-07-15 21:46:41 +10:00
Oliver
ea9623490d
Android sdk version (#509)
* Bump target SDK version

* Update pubspec and release notes

* Update sentry version

* Downgrade onecontext
2024-07-15 13:53:14 +10:00
Oliver
5a9a0b0855
New Crowdin updates (#507)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Romanian)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (French)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (French)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Arabic)

* New translations app_en.arb (Ukrainian)

* New translations app_en.arb (Estonian)

* New translations app_en.arb (Estonian)
2024-07-15 10:37:41 +10:00
Oliver
464d415115
Version update (#506) 2024-06-11 23:23:16 +10:00
Oliver
e837394495
Modern attachments (#505)
* Minimum API version is now 100

* Remove old API features

- Anything below API v100 no longer supported

* Reefactor attachment widget to support modern attachment API

* Filter and display attachments correctly

* Refactor
2024-06-11 23:16:01 +10:00
Oliver
c3eb1a5fca
New Crowdin updates (#504)
* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Romanian)
2024-06-11 22:14:20 +10:00
Oliver
b6d5d017ec
updates for iOS build (#503)
- Required to meet latest apple store
2024-06-04 16:35:40 +10:00
Oliver
aed07514dd
New translations app_en.arb (Portuguese) (#502) 2024-06-04 13:37:02 +10:00
Oliver
e600c5f6b5
Update release notes (#500) 2024-06-03 21:58:54 +10:00
Oliver
715cd06946
Long sn fix (#499)
* Improve tasks.py

- Works from any subdir now

* Update stock detail display

* FIx width of "serial" column in stock item list
2024-06-03 21:50:24 +10:00
Oliver
7575ba0136
New Crowdin updates (#497)
* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Portuguese)
2024-06-03 21:46:01 +10:00
João Nuno
4302f542dc
Update README.md (#496) 2024-06-01 09:21:28 +10:00
Oliver
349b3d4366
New Crowdin updates (#495)
* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish)
2024-05-30 19:03:36 +10:00
Oliver
9638b782cc
New Crowdin updates (#493)
* New translations app_en.arb (German)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Portuguese)
2024-05-21 09:02:29 +10:00
Oliver
541060aa03
Print label key (#491)
* We now use the plugin key for printing labels

* Bump API version for modern label printing

* Fix typo
2024-05-15 20:19:34 +10:00
Oliver
9a6e1e6381
New translations app_en.arb (Russian) (#490) 2024-05-12 20:42:08 +10:00
Oliver
3c0bca276d
Label printing fix (#489)
* Add check for modern label printing interface

* Update getLabelTemplates

* Fix typo

* Refactor / simplify

* Revert parameter type

* Update version number and release notes

* Refactor label printing function

- Will require some cleanup in the future
- Still needs testing

* Fix for modern printing

* Typo fix
2024-05-12 20:41:02 +10:00
Oliver
91cb24c74c
New translations app_en.arb (Hungarian) (#488) 2024-05-10 23:52:26 +10:00
Oliver
86425ebb2b
New translations app_en.arb (Ukrainian) (#487) 2024-04-29 20:28:59 +10:00
Oliver
177f040324
New Crowdin updates (#486)
* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))
2024-04-19 10:33:09 +10:00
Oliver Walters
f986e163e3 Bump version number 2024-04-18 22:54:19 +10:00
Oliver
4499f3e00e
Barcode workflow (#485)
* Refactor stock barcode operations into new file

* Add setting to control confirmation of stock transfer actions

* Update details when scannign stock item

* Confirm movement when moving items into location

* Cleanup
2024-04-18 22:53:21 +10:00
Oliver
a889417fe0
Company active filters (#484)
* Add support for "active" status for:

- SupplierPart
- Company

* Add filtering options

* Fix default value

* Add inactive tiles

* Update text and release notes
2024-04-18 21:48:45 +10:00
Oliver
0e658febe2
New Crowdin updates (#483)
* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Serbian (Latin))

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Czech)
2024-04-17 21:26:09 +10:00
Oliver
3f60b52a68
New Crowdin updates (#482)
* New translations app_en.arb (French)

* New translations app_en.arb (Czech)

* New translations app_en.arb (German)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovak)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Latvian)

* New translations app_en.arb (Serbian (Latin))
2024-04-14 07:35:04 +10:00
Oliver
4ae28d60a1
Readme updates (#476)
* Update README.md

- Add developer docs

* Remove RELEASE.md
2024-03-06 21:19:25 +11:00
Oliver
b02dc5bac7
Simplify DSN file (#475)
* Add checks for empty sentry DSN

* Add default DSN key

* Fix CI workflows
2024-03-06 21:09:06 +11:00
Oliver
a889c4adbe
Filter fix (#473)
* Add check for "null" top level locations and categories

* Fix API

- Top level location and category broken after API 174
- Ref: https://github.com/inventree/InvenTree/pull/6536
2024-02-28 16:23:46 +11:00
Oliver
1d41d229ca
Error report updates (#470)
* Prevent error reporting for certain status codes

- These error codes indicate that there is something wrong with the server setup
- Outside scope

* Bump version
2024-01-16 00:31:10 +11:00
Oliver
d152475de4
Catch errors (#469)
* Catch error comparing dropdown items

* Update version number and release notes

* Data conversion

* Catch error when loading image from network

* Suppress error reporting for statusCode -1
2024-01-10 23:24:40 +11:00
Oliver
4ef2e43bf3
Update version info (#467) 2023-12-14 00:14:29 +11:00
Oliver
571b491846
Sales order barcode (#466)
* Add barcode handler for allocating stock to sales order

* Refactor sales order allocation fields

* Improve barcode handling

* Handle barcode scan from sales order line detail view

* Remove debug statements
2023-12-13 23:49:55 +11:00
Oliver
edde9a9585
Update credits.md (#465) 2023-12-12 10:47:39 +11:00
Oliver
3ea5f8934c
Sales order allocation (#464)
* New string

* Typo fix

* Add model for SalesOrderShipment

* Add placeholder button to sales order item

* Create a new shipment from the sales order detail view

* Fix API URL

* Add paginated shipment list

* Upate colors

* Add API form for allocation of stock to sales order

* Build out sales order line detail widge

* Use unallocated quantity

* Update release notes

* linting fix
2023-11-27 22:51:20 +11:00
Oliver
70d0d4de93
Change rendering of part parameter (#463)
Closes https://github.com/inventree/inventree-app/issues/358
2023-11-27 22:09:39 +11:00
Oliver
1ec1a867d9
Add part to sales order via barcode scan (#461)
* Add part to sales order via barcode scan

* Update release notes

* Remove unused imports
2023-11-22 00:14:55 +11:00
Oliver
eb1be30df4
Fix display of sales order completion status (#460) 2023-11-21 08:23:09 +11:00
Oliver
711034f402
Update app release notes (#459) 2023-11-21 00:13:40 +11:00
Oliver
bf3df770c7
Po barcode scan (#458)
* Refactor existing barcode scan endpoint

- Break out into new file just for purchase orders

* Handle scanning of salesorder

* Add new handler for adding items to PO via barcode

* Allocate with barcode

* Add new string
2023-11-20 23:48:42 +11:00
Oliver
0a85441131
Purchase order updates (#456)
* Support supplierpart in related field

* Manual add line item to purchase order

* Update release notes
2023-11-15 23:53:47 +11:00
Oliver
1148f01d4a
New Crowdin updates (#455)
* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Bulgarian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Hindi)
2023-11-15 21:00:33 +11:00
Oliver
8cb5dd20f0
Barcode Scanning Updates (#448)
* Add new setting for controlling manual barcode scan

* Adds ability to pause and resume scanning with button

- Camera is still "live" during this

* Add UI elements

* Change scan setting

- "Continuous" scanning
- Enabled by default

* Update release notes

* Scanner updates

- Use "hold to pause" in continuous scan
- Use "tap to pause" in single scan

* Improve barcode scanning options

- Allow tap-to-pause or hold-to-pause
- More obvious user interactions

* Ensure consistent icon placement

* Remove separate setting for barcode pause mode
2023-11-14 07:39:06 +11:00
Oliver
20127c6090
New Crowdin updates (#454)
* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Finnish)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Traditional)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Bulgarian)
2023-11-13 20:48:28 +11:00
Oliver
bb87d0dd6d
Item deplete fix (#452)
* Add status code information to server error message

* More informative error message for 404

* Update release note
2023-11-12 23:43:52 +11:00
Oliver
e22ba95214
New translations app_en.arb (German) (#450) 2023-11-12 23:26:07 +11:00
Oliver
bdd5470e68
Sales order support (#438)
* Add new models for SalesOrder

- Create generic Order and OrderLine models with common functionality

* Refactor

- Move some widgets around
- Cleanup directory structure

* Add link to home screen and nav drawer

* Add SalesOrder list widget

* Linting fixes

* Fix string

* Refactor PurchaseOrderDetailWidget

* Tweaks to existing code

* linting

* Fixes for drawer widget

* Add "detail" page for SalesOrder

* Add more tiles to SalesOrder detail

* Allow editing of salesorder

* add list filters for sales orders

* Display list of line items

* Customer updates

- Display customer icon on home screen
- Fetch sales orders for customer detail page

* Cleanup company detail view

* Create new sales order from list

* Stricter typing for formFields method

* Create new PurchaseOrder and SalesOrder from company deatil

* Status code updates

- Add function for name comparison
- Remove hard-coded values

* Update view permission checks for home widget

* Add ability to manually add SalesOrderLineItem

* Add nice progress bar widgets

* Display detail view for sales order line item

* edit SalesOrderLineItem

* Fix unused import

* Hide "shipped items" tab

- Will be added in a future update
2023-11-12 23:13:22 +11:00
Oliver
c1c0d46957
New translations app_en.arb (Chinese Simplified) (#447) 2023-11-10 20:16:10 +11:00
Oliver
cf83ae86b9
New Crowdin updates (#443)
* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Bulgarian)
2023-11-09 20:49:01 +11:00
Oliver Walters
40fad26c06 Bump version number
- 0.13.0
2023-10-25 22:50:01 +11:00
Oliver
c641cea369
Scanner wedge mode (#437)
* Add code_scan_listener package

* Implement wedge controller widget

* Update barcode settings widget

- Allow user to choose which barcode scanner to use

* Fix typo

* Select barcode scanner widget based on user preference

* Fix rendering issues for wedge controller

* Update release notes

* Add unit test for wedge scanner widget

- Required some tweaks to other code

* Use better library

- https://github.com/fuadreza/flutter_barcode_listener
- Fork of https://github.com/shaxxx/flutter_barcode_listener
- Properly handles key "case" issues (shift, essentially)
- Verified that it works correctly for multiple character types

* Local copy of code, rather than relying on package which is not available on pub.dev

* Fix unit test
2023-10-25 22:40:49 +11:00
Oliver
b6ab9d5da5
Refresh fix (#436)
* Add notificationPredicate to improve scroll-to-reload

Ref: https://api.flutter.dev/flutter/material/RefreshIndicator-class.html

* Add scroll-to-refresh for paginated list
2023-10-23 21:59:05 +11:00
Oliver
8f1cd1cae1
Prevent notification dismissal from ocurring multiple times (#435) 2023-10-23 21:37:33 +11:00
Oliver
76b6191a67
Token auth (#434)
* Embed device platform information into token request

* Remove username and password from userProfile

* Display icon to show if profile has associated user token

* Remove username / password from login settings screen

* Refactor login procedure around token auth

* Refactoring

* Add profile login screen

- Username / password values are not stored
- Just to fetch api token

* Login with basic auth

* Pass profile to API when connecting

* Remove _BASE_URL accessor

- Fixes URL caching bug

* Add more context to login screen

* Add helper functions for unit tests

- Change default port to 8000 (makes testing easier with local inventree instance)

* api.dart handles basic auth now

* fix api_test.dart

* Further test improvements

* linting fixes

* Provide feedback when login fails

* More linting

* Record user details on login, and display in "about" widget

* Fix string lookup

* Add extra debug

* Fix auth values

* Fix user profile test
2023-10-23 01:29:16 +11:00
Oliver
382c8461f9
New translations app_en.arb (Japanese) (#433) 2023-10-21 23:24:02 +11:00
Oliver
0d44bd3799
Update release_notes.md (#432) 2023-10-19 23:35:13 +11:00
Oliver
c65833cf6d
New Crowdin updates (#431)
* New translations app_en.arb (German)

* New translations app_en.arb (Japanese)
2023-10-19 23:29:33 +11:00
Bobbe
67fd6a564a
Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint (#421)
* Add POReceiveBarcodeHandler to support barcode/po-receive/ endpoint

* Remove german translation

* Add api version checks

* Add getOverlayText method to barcode handler

* Bump required API version to 139

* Update barcode.dart

The "quantity" field is not an integer, and can cause the app to crash if not handled correctly

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2023-10-19 23:28:32 +11:00
Oliver
c76309341b
New translations app_en.arb (German) (#430) 2023-10-08 20:18:14 +11:00
Oliver
f4be87e826
New Crowdin updates (#429)
* New translations app_en.arb (Russian)

* New translations app_en.arb (Chinese Simplified)
2023-10-05 00:17:44 +11:00
Oliver
a119bcc465
New translations app_en.arb (Spanish, Mexico) (#427) 2023-10-01 13:46:37 +11:00
Oliver
3cc57101c4
Android: reduce minSdkVersion to 21 (#426)
Fixes https://github.com/inventree/inventree-app/issues/425
2023-09-21 21:17:55 +10:00
Oliver
6520873384
Translation fix (#424)
* Fix locale header

- Moving to new framework meant that Intl.getCurrentLocale was no longer working

* Update release notes

* Fix typo in pubspec.yaml

* Clear cached values when locale is changed

* Add extra context check
2023-09-20 08:37:48 +10:00
Oliver
dd12769a51
Stock transfer extra (#420)
* Add API version check

* Support "packaging" and "status" fields when performing a stock-transfer action

* Update release notes
2023-09-09 00:32:57 +10:00
Oliver
9203ee8a3f
New Crowdin updates (#417)
* New translations app_en.arb (Swedish)

* New translations app_en.arb (Russian)
2023-08-25 23:31:45 +10:00
Oliver
1960016288
Do not run flutter upgrade (#418) 2023-08-25 23:22:14 +10:00
Oliver
81907ad72f
Flutter upgrades (#416)
* Upgrade flutter version

* Update packages

* update prj files

* Bump android sdk target to 33
2023-08-22 22:52:18 +10:00
Oliver
af09cde29e
New translations app_en.arb (French) (#414) 2023-08-21 17:50:11 +10:00
Oliver
32c301a9b1
Show version (#415) 2023-08-21 17:43:19 +10:00
Oliver Walters
38dfb03669 Update release notes 2023-08-12 20:59:15 +10:00
Oliver
e6ad1bcb98
New translations app_en.arb (Turkish) (#413) 2023-08-12 20:44:59 +10:00
Oliver
8200140976
Label fix (#411)
* Cleanup label printing options

- Improve calls to setState()
- Should fix potential race conditions

* Use name if description not available

* Code simplification

* Fetch plugins even if the server reports "plugins enabled"

- Builtin plugins are still a thing!

* Use name *and* description to display label
2023-08-12 20:41:11 +10:00
Oliver
d81f0d532d
New translations app_en.arb (Russian) (#412) 2023-08-10 22:32:00 +10:00
Oliver
75c4e038f4
New translations app_en.arb (Vietnamese) (#410) 2023-08-09 21:57:41 +10:00
Oliver
72f243e1a5
New Crowdin updates (#409)
* New translations app_en.arb (Swedish)

* New translations app_en.arb (Hindi)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Vietnamese)
2023-08-08 20:30:44 +10:00
Oliver
d2a01a0286
Supplier part fix (#408)
* Change supplier part fields based on API version

* Display packaging info on supplier part page

* Icon consolidation

* Bump version number
2023-07-27 10:16:36 +10:00
Oliver
d6460d58aa
New Crowdin updates (#407)
* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)
2023-07-27 09:50:19 +10:00
Oliver Walters
e549968d58 Missed update to pubspec.yaml 2023-07-23 12:43:34 +10:00
Oliver
b044c53d91
More debug (#405)
* Extra options for sentry

* Use string comparison

* Catch error when constructing related field

* Include field name in debug
2023-07-23 09:55:08 +10:00
Oliver
2e2e9640d4
New translations app_en.arb (Hungarian) (#404) 2023-07-22 22:17:54 +10:00
Oliver
174f1b2f2d
New Crowdin updates (#402)
* New translations app_en.arb (Dutch)

* New translations app_en.arb (Chinese Traditional)
2023-07-21 22:28:43 +10:00
Oliver
7a11fdead8
Tweaks (#401)
* Improve collect_translations.py

* Cleanup

* Update translation support

* Update release notes
2023-07-17 21:17:23 +10:00
Oliver
3085d98ce1
New translations app_en.arb (Chinese Traditional) (#400) 2023-07-17 20:59:39 +10:00
Oliver
443e6e856c
Label print updates (#399)
* Allow download of printed label

* Add setting for controlling label printing

* Control display of label printing via setting

* Refactor label printing functionality

- Move to helpers.dart
- Will be used for other label types also

* Factor out request for label templates

* Add label printing support for part

* Support label printing for stock location

* update release notes
2023-07-16 00:51:11 +10:00
Oliver
d78affc1cb
New Crowdin updates (#397)
* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (French)
2023-07-13 22:05:10 +10:00
Oliver
76457793b3
Bump version to 0.12.5 (#396) 2023-07-07 22:04:21 +10:00
Oliver
7ef6da4b2a
Add extra filtering options for stock items (#394) 2023-07-07 21:47:27 +10:00
Oliver
637b058a8a
New Crowdin updates (#393)
* New translations app_en.arb (Turkish)

* New translations app_en.arb (Turkish)
2023-07-07 21:47:15 +10:00
Oliver
2babf27db5
Sentry fix (#395)
* Extra sentry diagnostics

* Fix unit test

* Unit test updates

* More unit test updates
2023-07-07 21:38:04 +10:00
Oliver
6fe23fa846
Update release notes (#392)
* Update release notes

* Fix typo
2023-07-05 22:21:12 +10:00
Oliver
e39ab9ad78
New Crowdin updates (#391)
* New translations app_en.arb (German)

* New translations app_en.arb (French)
2023-07-05 09:35:56 +10:00
Oliver
9277e18028
New Crowdin updates (#390)
* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Russian)

* New translations app_en.arb (French)

* New translations app_en.arb (Portuguese, Brazilian)
2023-07-04 19:27:45 +10:00
Oliver
0365557475
Tweaks for home page widget (#389) 2023-07-02 20:24:07 +10:00
Oliver
138cae2da0
Search improvements (#388)
* Refactor search widget

- visual improvements
- Simplifications
- Add refresh button
- Improve search button

* Track original search

* fix BOM widget

* Update release notes
2023-06-30 22:42:59 +10:00
Oliver
23abcb48f2
New Crowdin updates (#387)
* New translations app_en.arb (Russian)

* New translations app_en.arb (Russian)
2023-06-30 22:30:51 +10:00
Oliver
320b16f86e
Specify batch code for incoming items (#386)
When receiving against purchase order
2023-06-29 22:48:14 +10:00
Oliver
279c15509c
Pre fill location (#384)
* Pre-fill the location when transferring stock

* Update release notes
2023-06-28 21:25:15 +10:00
Oliver
d0d96166c4
New translations app_en.arb (Dutch) (#383) 2023-06-28 21:10:09 +10:00
Oliver
08ebc34730
Bump version to 0.12.3 (#382)
* Bump version to 0.12.3

* Updates for ios

* Fix for float / decimal field

* Cleanup simpleNumberString

* Increment build number
2023-06-26 20:53:09 +10:00
Oliver
925966c627
Barcode refactor (#381)
* Simplify barcode scanning interface

* Use consistent colors

* Fix notches

* Remove old comment
2023-06-24 21:06:08 +10:00
Oliver
e9eb84eace
Stock display (#379)
* Display stock quantity more prominently

* Cleanup search widget

* Update for stock_detail widget

* More tweaks

* Change bottom bar icon

* Display boolean parameters appropriately

* Adds ability to edit part parameters

* Bump icon size a bit

* Improvements to filter options

- Allow filtering by "option" type
- To start with, filter stock by status code

* Remove debug message

* Remove getTriState method

- No longer needed
- Remove associated unit tests

* Adjust filters based on server API version

* Muted colors
2023-06-24 11:34:42 +10:00
Oliver
8076887e39
New translations app_en.arb (Portuguese, Brazilian) (#377) 2023-06-22 20:04:40 +10:00
Oliver
770d9cddb2
Add ability to filter stock by "in_stock" (#376) 2023-06-20 20:38:07 +10:00
Oliver
001450d3bb
New Crowdin updates (#373)
* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Spanish, Mexico)
2023-06-18 00:23:13 +10:00
Oliver Walters
b863bbd590 Update info.plist 2023-06-14 19:56:17 +10:00
Oliver
ca678c1c6f
Update version (#372)
* Update version

- Bump version to 0.12.2

* Tweak unit test
2023-06-13 21:28:38 +10:00
Oliver
367759e86c
Ignore barcode resume if not mounted (#371) 2023-06-13 20:00:59 +10:00
Oliver
9cc666d13e
Fix bug when list of tiles flows off screen (#370)
- Reimplement scrolling behaviour
2023-06-13 19:58:41 +10:00
Oliver
71bf3ad049
App orientation (#369)
* Configurable screen orientation

- Follow system (default)
- Fixed in portrait
- Fixed in landscape

* Fix for dialog
2023-06-13 19:53:50 +10:00
Oliver
ba409660f4
More iOS workflow fixes (#367)
* More iOS workflow fixes

* revert to building android in debug

- Needs signing key otherwise
2023-06-12 23:29:45 +10:00
Oliver
8cebc25bea
Fix iOS workflow (#366) 2023-06-12 23:10:19 +10:00
Oliver
21ace1ae02
Package updates (#365)
* Update dart version

* Update flutter version in workflow

* Update packages

* Updates to android workflow

* Specify dart verrsion in CI

* Run flutter upgrade

* Helps to check which workflow is actually running I guess

* Disable linting check

* linting fixes

* linting

* Bug fix for paginator
2023-06-12 22:52:07 +10:00
Oliver
b051aeccda
Barcode refactor (#363)
* Move barcode.dart

* Fix

* Refactoring barcode scanner code:

- Abstract the "controller" class (for future development)
- Break barcode scanning code out into multiple files
- Add CameraBarcodeController class (qr_code_scanner)

* Add await

* Make barcode scan delay configurable

* remove unused import

* Handle camera exceptions

* Improve sequencing for camera scanner

- Show loading overlay
- Prevent reload if view is no longer mounted

* Update docstring

* Update release notes
2023-06-11 09:41:26 +10:00
Oliver
45fe79daf0
New translations app_en.arb (Finnish) (#362) 2023-06-10 20:17:40 +10:00
Oliver
7ca9a7ccc4
New Crowdin updates (#360)
* New translations app_en.arb (Finnish)

* New translations app_en.arb (Finnish)
2023-06-06 21:07:39 +10:00
Oliver
973f1fb002
New translations app_en.arb (Finnish) (#357) 2023-05-30 20:19:14 +10:00
Oliver
b733d00c37
Fix bug in purchase order form (#354)
- Remove "project_code" field if the server API does not yet support it
2023-05-18 20:55:50 +10:00
Oliver
905cedf9af
New Crowdin updates (#350)
* New translations app_en.arb (Czech)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Spanish, Mexico)
2023-05-13 20:48:38 +10:00
Oliver
230627e2bb
iOS Build Tweaks (#349)
* Update package versions

* Update xcode project

* Further package updates

* Add display name for ios
2023-05-01 09:35:23 +10:00
Oliver
0296c4c22a
Ios build (#348)
* Update package versions

* Update xcode project

* Further package updates
2023-04-30 21:52:32 +10:00
Oliver
4ba19fcab2
Increment to 0.12.0 (#347) 2023-04-29 21:06:12 +10:00
Oliver
99c768a2e1
New translations app_en.arb (Portuguese, Brazilian) (#346) 2023-04-29 20:06:35 +10:00
Oliver
383571707e
Stock test actions (#345)
* Use FAB for stock item test result

* Change long press to tap

* Add setting to control display of stock tests results

* Add question mark if no result recorded
2023-04-28 23:27:59 +10:00
Oliver
49226a5fce
New Crowdin updates (#343)
* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Vietnamese)
2023-04-27 19:09:19 +10:00
Oliver
7abb8cf0c0
New Crowdin updates (#341)
* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (German)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (French)

* New translations app_en.arb (Norwegian)
2023-04-22 22:26:16 +10:00
Oliver
b7e806efee
PO Line Item Improvements (#340)
* Refactor thumbnail image

* Add paginated list of purchase order line items

* Refactor getBody() function

- No longer "have" to specify
- Can use getTiles for a simpler interface

* Add detail widget for polineitem

* add pricing info

* Receive line items via action button

* tweak color

* update release notes

* linting fixes
2023-04-21 23:15:00 +10:00
Oliver
2c5ceeabdb
Handle OSError when attempting connection (#339) 2023-04-21 21:24:14 +10:00
Oliver
bb781aaed5
Allow stock items to be created from top-level location (#338)
(cherry picked from commit fd85abf9d8e4760dd3ed793cf38d3bc5c29f17b8)
2023-04-21 21:24:06 +10:00
Oliver
e23a8b4d5e
Project code support (#336)
* Determine if project codes are supported

* Add helpers for boolean functions

* Adds helper methods for generic "model" class

- Will allow us to do some good refactoring

* Refactor the refactor

* Add debug support and getMap function

* Major refactoring for model data accessors

* Handle null values

* Add sentry reporting if key is used incorrectly

* Fix typo

* Refactor createFromJson function

* Add model for ProjectCode

* Display and edit project code for purchase orders
2023-04-21 21:12:22 +10:00
Oliver
95573a2784
New Crowdin updates (#335)
* New translations app_en.arb (Czech)

* New translations app_en.arb (Czech)
2023-04-21 19:35:07 +10:00
Oliver
232f721712
Fix duplicate serial number field (#334) 2023-04-20 20:47:40 +10:00
Oliver
1d6708fbca
Adjust colors of action buttons (#333)
Fixes https://github.com/inventree/inventree-app/issues/330
2023-04-20 20:47:24 +10:00
Oliver
ac57c53202
Bump version number to 0.11.6 (#332) 2023-04-20 18:55:22 +10:00
Oliver
0c4179480d
New translations app_en.arb (Norwegian) (#331) 2023-04-20 18:41:03 +10:00
Oliver Walters
caa4fdd2a1 Tweak to get invoke command to work on mac 2023-04-19 23:05:16 +10:00
Oliver
8510034a81
Bump build version to 0.11.5 (#328) 2023-04-19 22:04:18 +10:00
Oliver
b9ffabd561
Make notes widget "generic" (#327)
* Make notes widget "generic"

- No longer tied to the "part" model
- Will allow us to use it elsewhere

* Update release notes

* Add helper methods for checking model permissions

* Refactoring of permissions checks

* Add notes to the "purchase order" widget

* Fix typos

* remove bom tab from part view

* linting fixes
2023-04-19 21:57:28 +10:00
Oliver
28ed1ed545
Improve supplier part detail screen (#326)
* Improve supplier part detail screen

* Update release notes
2023-04-19 21:17:24 +10:00
Oliver
87994a4912
Re-implement BOM link from part view (#325) 2023-04-19 21:07:56 +10:00
Oliver
ba1df2e817
Adds invoke script for automating release tasks (#324) 2023-04-19 20:57:40 +10:00
Oliver
347692bf70
Add transparent background image (#323)
- Fixes display of background image in dark mode
2023-04-19 20:54:52 +10:00
Oliver
612db9f194
Improvements for dark mode (#322)
* Action colors are now determined based on theme

* Fix unused import

* Update some more colors based on theme

* Updated release notes

* Better color choice

* Update for home screen

* Updates for app drawer

* remove unused import
2023-04-18 23:07:24 +10:00
Oliver
d926686a89
Stock history fix (#320)
* Improves quantity parsing from

* Add paginated history widget

* Refactor stock history widget as a paginated widget

* Allow paginated result list to handle results returned as list

- Some API endpoints (older ones most likely) don't paginate results correctly

* Fix code layout

* Render user information in "history" widget (not quantity)

* Hide filter button

* Update release notes

* remove unused import
2023-04-18 22:46:08 +10:00
Oliver
01a45568a0
Bump version to 0.11.4 (#321) 2023-04-18 22:39:32 +10:00
Oliver
b5c4bda80f
New translations app_en.arb (French) (#319) 2023-04-18 18:43:52 +10:00
Oliver
b54565d1c3
Dark mode color fix (#318)
* Fix custom colors in API forms

* update release notes

* update build version
2023-04-16 22:07:01 +10:00
Oliver
a3d712d11d
Adds "dark mode" support (#317)
* Adds "dark mode" support

- Uses adaptive_theme package

* CI fixes

* More fixes

* Update release notes
2023-04-16 21:10:57 +10:00
Oliver
e7f5141aa9
New translations app_en.arb (Hungarian) (#315) 2023-04-15 22:11:55 +10:00
Oliver
1d913084a6
Update release notes (#314) 2023-04-12 22:58:13 +10:00
Oliver
943104f20c
Add actions to issue or cancel purchase orders (#313) 2023-04-11 22:52:27 +10:00
Oliver
164295c3e2
Code Cleanup (#312)
* Open email and telephone links for company

* Cleanup imports
2023-04-11 22:38:57 +10:00
Oliver
946abb60a0
Update notifications periodically (#311)
- Call periodically from API class
2023-04-11 21:25:39 +10:00
Oliver Walters
0156329fb6 Bump release number 2023-04-11 00:00:43 +10:00
Oliver
2fdff02299
Update version number (#309) 2023-04-10 23:29:29 +10:00
Oliver
cb5c292326
Dialog improvements (#308) 2023-04-10 23:28:02 +10:00
Oliver
26b86a2194
Update status codes (#307)
* Extra error info when connecting to server

* Adds accessors for various types of status codes

* Cleanup / refactor stock status codes

- No longer need to duplicate these on the app

* improvements to purchase order list

- Add option to show closed orders
- Render order status

* Add purchase order status to order detail widget

* Update release notes

* Cleanup for imports

* linting fixes
2023-04-10 23:12:30 +10:00
Oliver
efb7ff4170
Keyboard fix (#306)
* Remove focusNode in search widget

- Was causing some issues in iOS in particular

* Improve search UX by cancelling previous query

- Prevent successive search queries from being displayed

* Update release notes

* Add "type" for cancelable operation
2023-04-10 17:07:06 +10:00
Oliver
020cc4497c
PO Contact (#305)
* Bug fix in API forms

- Allow form fields to specify custom filters at runtime

* Add "contact" model to purchase order edit form

* Add action to create new purchase order from list widget

* widget updates for purchase order
2023-04-10 16:59:45 +10:00
Oliver
8631fedbfb
New translations app_en.arb (French) (#304) 2023-04-10 16:23:22 +10:00
Oliver
bf8a65fadf
Update release notes (#302) 2023-04-09 00:14:09 +10:00
Oliver
a8f87e2f5a
UX Overhaul (#300)
* Add "global actions" to title bar

* Implement actions

* Add "speed dial" action buttons

* tweak global action icons

* Refactor actions for "stock item" display

* Refactor "part" detail

* part category

* SupplierPart

* More updates

* Add BottomAppBar

* Add a global bottom app bar

* Move "edit" buttons back to the app bar

* tweaks

* Updates to drawer navigation menu

* home screen improvements

* text tweaks

* Fix appBarTitle for notifications widget

* Update "tabs" for category display

* Fix for attachment widget

* Update tabs for purchaseorder view

* Update part display

* Cleanup

* Add "BOM" tab to part detail widget

* Paginated list search cleanup

* Update release notes

* Update old function

* linting

* linting

* Tweaks to bottomappbar

- Increase icon size slightly
- Adjust "actions" icon
2023-04-08 23:59:11 +10:00
Oliver
74176cdda8
Bump release noes (#299) 2023-04-05 23:14:39 +10:00
Oliver
f7d3315c99
Support barcode scan for purchase order (#298)
* Add functions for determining API support levels

* Handle scanning of purchase orders
2023-04-05 22:43:29 +10:00
Oliver
fb0a383fff
New Crowdin updates (#297)
* New translations app_en.arb (French)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Danish)

* New translations app_en.arb (German)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Slovenian)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Thai)
2023-04-05 22:12:17 +10:00
Oliver
79026792e2
remove legacy "reference_prefix" request (#295) 2023-04-04 22:34:06 +10:00
Oliver
091f33eb10
Update version and build (#290) 2023-03-24 21:19:26 +11:00
Oliver
d7f2c3939b
Consolidated search (#289)
* Update search widget to support consolidated search API endpoint

* Finer control over global search

* Update release notes

* remove unused variable
2023-03-24 21:09:38 +11:00
Oliver
9543490c21
New Crowdin updates (#288)
* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Portuguese, Brazilian)
2023-03-24 19:37:45 +11:00
Oliver
73fd35d9a3
New translations app_en.arb (Hungarian) (#283) 2023-03-17 19:36:44 +11:00
Oliver
b2d4522fb2
Add currency suffix if currency cannot be determined (#282) 2023-03-15 07:56:27 +11:00
Oliver
82f25dfc90
Update docs link (#281) 2023-03-14 23:02:59 +11:00
Oliver
878d9b46d2
New Crowdin updates (#280)
* New translations app_en.arb (German)

* New translations app_en.arb (German)
2023-03-14 22:57:10 +11:00
Oliver
bb1c1cf3d9
New translations app_en.arb (Portuguese, Brazilian) (#279) 2023-03-09 20:35:21 +11:00
Oliver
347e80d8e2
Adds support for currency display (#277)
* Adds a helper function for rendering currency data

* Update helper functions for StockItem model

* Render purchasePrice correctly for stockitem

* Use currency_formatter library instead of money_formatter

* Add total price display for purchase order

* icon fix
2023-03-08 23:42:26 +11:00
Oliver
221920cbbe
New Crowdin updates (#276)
* New translations app_en.arb (Czech)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Portuguese, Brazilian)
2023-03-03 18:57:52 +11:00
Oliver
10ae5e47ba
Update version number to 0.10.2 (#275) 2023-02-27 21:18:48 +11:00
Oliver
ee0a6815f4
Update for credits display (#274)
- Cleaner display
- Links are now clickable
2023-02-27 21:13:26 +11:00
Oliver
84f7e90569
Fix icon on SupplierPartDetail widget (#273)
* Fix icon on SupplierPartDetail widget

* Update release notes
2023-02-27 21:01:17 +11:00
Oliver
c8dedf2a0e
Bump version number to 0.10.1 (#272) 2023-02-26 22:17:04 +11:00
Oliver
4b8ab304aa
New translations app_en.arb (Portuguese, Brazilian) (#271) 2023-02-26 22:08:58 +11:00
Oliver
fb80029c0e
Barcode camera fix (#270)
* Bug fix for multiple barcode scans

- Do not resume scan until action is performed
- Add in delay before re-starting camera

* Change release notes to 0.10.1

* linting fixes
2023-02-22 00:52:26 +11:00
Oliver
5c06e3c9de
Update credits.md (#268)
Add simonkuehling as contributor
2023-02-19 21:08:02 +11:00
simonkuehling
113b3d69a9
skip SnackIcon banner when opening a view from successful barcode scan (#266) (#267) 2023-02-19 21:07:47 +11:00
Oliver
80b83b842d
New Crowdin updates (#262)
* New translations app_en.arb (Italian)

* New translations app_en.arb (Chinese Simplified)
2023-02-18 08:13:59 +11:00
Oliver
9485d858eb
Add support for company attachments (#261)
* Add support for company attachments

- Add API version check
- Add new class
- Add link to company detail page
- Assorted refactoring

* linting fixes
2023-02-16 22:50:32 +11:00
Oliver
c7527e8b4e
New translations app_en.arb (French) (#259) 2023-02-16 08:40:08 +11:00
Gustaf Järgren
262a629923
Bumped minimum iOS version (#258) 2023-02-11 08:43:36 +11:00
Gustaf Järgren
7447a18e64
Fix iOS CI invalid syntax (#257) 2023-02-11 07:13:09 +11:00
Oliver
0493bb2a12
Update credits (#256)
* Add contributors section

(cherry picked from commit 757ac25d4c30191786e83a6acb492060d6a5411a)

* Fixes for credits

(cherry picked from commit b2737624f946b4b525663e32d81e2951d4382e78)

* Add gorymoon
2023-02-11 00:24:22 +11:00
Gustaf Järgren
6d4973deb8
Updated dependencies (#255)
* Bump android compile and target version, also flutter dependencies and resolve issues

* Remove deprecated splashscreen and added support for new Android 12 version.

* Updated workflow action versions and flutter sdk

* Resolved linting issues

* Resolved test binding issues
2023-02-11 00:24:06 +11:00
Oliver
298ee24a9c
New Crowdin updates (#254)
* New translations app_en.arb (German)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish, Mexico)
2023-02-10 21:22:54 +11:00
Oliver
15bf109296
Supplier part support (#253)
* Bump version and release noes

* Add barebone list and detail widgets for the SupplierPart model

* Launch into SupplierPartList from CompanyDetail

* Update StockDetail widget

* Fixes for SupplierPart model

* Add images to supplier part list

* Add search functionality to SupplierPart list

* Added details to SupplierPartDetail widget

* Link through to supplier company

* Add some more details

* Adds ability to edit SupplierPart information

* Navigate to supplier part list from part detail page

* Display supplier part information on stock item detail page

* Add barcode scan response for SupplierPart

* Refactor barcode scanning code

* Navigate to supplier part detail from stock item page

* Support custom barcode for SupplierPart via app

* Cleanup comment

* linting

* Fix override

* Enable display of supplier list on home screen

* Code cleanup

* Update release noets
2023-02-04 09:05:36 +11:00
Oliver
ce37d0e757
New Crowdin updates (#252)
* New translations app_en.arb (German)

* New translations app_en.arb (German)
2023-02-03 12:53:30 +11:00
Oliver Walters
dece29d1d5 Update version to 0.9.3 2023-01-28 00:13:24 +11:00
Oliver
9791fb7d23
Update plugin metadata endpoint (#251)
* Update plugin metadata endpoint

* Update release notes
2023-01-27 23:47:48 +11:00
Oliver
e9d9cf5322
Handle case where streaming respone has invalid length (#250) 2023-01-27 23:23:11 +11:00
Oliver
20de6e03e6
New translations app_en.arb (Italian) (#249) 2023-01-27 23:13:40 +11:00
Oliver
614253b901
New Crowdin updates (#244)
* New translations app_en.arb (Czech)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (German)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (French)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Italian)
2023-01-26 17:30:43 +11:00
Oliver Walters
ba72781df8 Bump version number 2022-12-28 21:37:15 +11:00
Oliver
83da0638a8
Dialog fixes (#242)
* Fix dialog for purchase order line item

* Fix other dialogs too

* Update release noes
2022-12-28 00:09:26 +11:00
Oliver
1ab3940e6a
Notification fixes (#240)
* Show snack bar for shorter duration if no associated action

* Dismiss snack bars with tap

* Upate release notes
2022-12-24 22:56:27 +11:00
Oliver
d69ffc8de0
Update notes for building under iOS (#238) 2022-12-21 23:00:10 +11:00
Oliver
0090443495
New translations app_en.arb (Hungarian) (#237) 2022-12-21 21:41:45 +11:00
Oliver
f339ac249b
Adds pre-generated file for mapping icon names to icon hex values (#235)
* Adds pre-generated file for mapping icon names to icon hex values

* Remove unused import

* Simple method for converting icon string into useable data

* Add rendering for category list and location list

* Add custom icons to detail views

* Updated release notes
2022-12-18 00:06:11 +11:00
Oliver
8639ccf79e
New Crowdin updates (#233)
* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Slovenian)
2022-12-17 20:02:19 +11:00
Oliver Walters
499bd3edf8 Bump version number 2022-12-11 23:42:19 +11:00
Oliver
d2b74e7684
Stock barcode fix (#232)
* API: Provide more info in error messages

* Fix support for legacy stock item custom barcodes

* Refresh display after assigning barcode

* Update release notes

* Fix for scanning unkown barcode

- Modern API returns slightly different data

* Fix for scanning unkown barcode

- Modern API returns slightly different data

* Update release notes
2022-12-11 23:41:21 +11:00
Oliver
27040024c0
New Crowdin updates (#230)
* New translations app_en.arb (Dutch)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (French)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Spanish, Mexico)
2022-12-11 22:21:46 +11:00
Oliver Walters
da79524dd1 0.9.0
Update release notes
2022-12-06 19:16:15 +11:00
Oliver
d015a3087d
New translations app_en.arb (Spanish) (#229) 2022-12-06 19:13:56 +11:00
Oliver
730521fd00
New barcode actions (#218)
* Bump release notes

* Adds method for linking custom barcodes

* Custom getter method for determining if an item has barcode data

* Add method to check if the API supports "modern" barcodes

* Refactor custom barcode implementation for StockItem

- Needs testing

* Unit testing for linking and unlinking barcodes

* Fixes

* Refactor code for "custom barcode action" tile

* Add custom barcode action to StockLocation

* Add extra debug to debug the debugging

* Unit test fix

* Change scope I guess?

* remove handler test
2022-12-05 23:39:40 +11:00
Oliver
efb6fc353e
Bom optional (#227)
* Split part settings onto different screen

* show_bom setting controls whether BOM information is displayed

* linting
2022-12-04 23:05:35 +11:00
Oliver
971c6bfcdb
Update API for marking a notification as "read" (#228) 2022-12-04 23:01:11 +11:00
Oliver
544b270ac5
New translations app_en.arb (Hungarian) (#225) 2022-12-04 22:50:34 +11:00
Oliver
c2574e9fa5
Part parameters (#224)
* Adds class representing the PartParameter model

* Adds API method for determining support for PartParmaters

* Display part parameter count in part detail widget

* Adds user setting for controlling if part parameters are displayed

* Fix URL for model

* Widget for displaying part parameters

* linting
2022-12-04 20:51:48 +11:00
Oliver
207e5ec6c5
Adds option for deleting attachments from the app (#222)
* Adds option for deleting attachments from the app

Closes https://github.com/inventree/inventree-app/issues/153

* Add entry to release notes
2022-12-02 22:10:56 +11:00
Oliver
15b4cbc67a New translations app_en.arb (Spanish) 2022-11-30 07:28:11 +11:00
Oliver
a951e75a7e New translations app_en.arb (Spanish) 2022-11-29 07:31:20 +11:00
Oliver
d122a352a6
Structural categories (#220)
* Add support for editing "structural" tree field

- Requires API v83 or newer

* Update release notes
2022-11-28 20:18:57 +11:00
Oliver Walters
30ea893023 Merge remote-tracking branch 'origin/l10n_master' 2022-11-28 19:02:15 +11:00
Oliver
b07290fee4 New translations app_en.arb (Spanish, Mexico) 2022-11-24 03:52:34 +11:00
Oliver
9475829d86 New translations app_en.arb (Spanish, Mexico) 2022-11-23 15:29:29 +11:00
Oliver
881bc0b6d9 New translations app_en.arb (Spanish) 2022-11-16 20:21:15 +11:00
Oliver
4833424686 New translations app_en.arb (Spanish) 2022-11-16 08:18:28 +11:00
Oliver
c87021fa67 New translations app_en.arb (Turkish) 2022-11-15 17:11:54 +11:00
Oliver
3c36215820 New translations app_en.arb (Greek) 2022-11-15 17:11:53 +11:00
Oliver
f289ab637c New translations app_en.arb (Portuguese, Brazilian) 2022-11-14 16:57:42 +11:00
Oliver
f196ad7fb7 New translations app_en.arb (Portuguese, Brazilian) 2022-11-14 04:44:25 +11:00
Oliver
ca96a707f5 New translations app_en.arb (Portuguese, Brazilian) 2022-11-11 14:14:00 +11:00
Oliver
6401e5c81b
New Crowdin updates (#216)
* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Spanish, Mexico)
2022-11-06 20:06:35 +11:00
Oliver
8f0d5add44
Add backup dir for InvenTree test server (#215)
* Add backup dir for InvenTree test server

* Specify python version
2022-11-06 19:57:02 +11:00
Oliver
e2e47961fd
New translations app_en.arb (Spanish, Mexico) (#214) 2022-10-28 12:32:50 +11:00
Oliver
55d1b7a060
New Crowdin updates (#213)
* New translations app_en.arb (Spanish)

* New translations app_en.arb (German)

* New translations app_en.arb (Russian)
2022-10-06 22:46:19 +11:00
Oliver
6d796a2e32
Barcode updates (#211)
* Adds API function for unlinking a barcode

* Show barcode unlink result

* Update release notes and version number
2022-09-15 14:22:40 +10:00
Oliver Walters
87c8a21c3c Merge remote-tracking branch 'origin/l10n_master' 2022-09-14 18:42:34 +10:00
Oliver
ac52feb97b New translations app_en.arb (Danish) 2022-09-14 14:38:58 +10:00
Oliver
7fc109e0c2
Show used in assembly list (#209)
* Update default list filters for BomItem

* Display "usedIn" count for part detail view

* Improve BillOfMaterials widget to display "used in" parts

* Update release notes
2022-09-10 14:06:58 +10:00
Oliver
c25175ac54
Search fix (#208)
* Prevent setState if search widget is not mounted

* Prevent search textbox from disappearing
2022-09-10 13:11:34 +10:00
Oliver
38b34e0fd9
New Crowdin updates (#204)
* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (French)

* New translations app_en.arb (Czech)

* New translations app_en.arb (German)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Greek)

* New translations app_en.arb (Thai)

* New translations app_en.arb (French)
2022-09-05 21:35:03 +10:00
Oliver
e7c5186823
Catch some subtle errors (#202)
- Prevent API requests for invalid PK values
- Perform checks for invalid PK values at multiple points
- Change order of operations in StockDetail widget
2022-08-04 13:23:29 +10:00
Oliver
61bacefd36
Show release notes (#201)
* Add function to set app locale

* Setting for selecting app language

- Adds requirement for "flutter_localized_locales"
- Change main app to stateless

* Reload entire app tree when language is changed

* Update release notes

* linting

* Show the release notes after updating to a new app version
2022-08-03 11:55:46 +10:00
Oliver
9559b8602e
Locale switch (#200)
* Add function to set app locale

* Setting for selecting app language

- Adds requirement for "flutter_localized_locales"
- Change main app to stateless

* Reload entire app tree when language is changed

* Update release notes

* linting
2022-08-02 15:54:59 +10:00
Oliver
19ff6eb526
Stock item serial number functionality (#199)
* Stock item serial number functionality

- Allow serial numbers to be specified when creating a new stock item
- Allow serial number to be edited from stock item context

* Update build number and release notes
2022-08-02 13:34:15 +10:00
Oliver
e78bb78bfd
Check status code before throwing JSON error (#198)
* Check status code before throwing JSON error

* Update release notes
2022-08-01 13:18:26 +10:00
Oliver
2cbcf275ab
Api filtering (#197)
* Add extra filtering options for the PartCategory list

(cherry picked from commit c7b594fd0bfafdde1298f8d3cf503871762b49eb)

* Extra filtering options for stock location list

(cherry picked from commit 5e9bfcfbc336831bfd2bb4414fc1794420c89e0d)
2022-08-01 12:25:13 +10:00
Oliver
b5d26580b4
Update API to match server changes (#196)
* Support updated API which changes detail of PartCategory list

* Update version to 0.8.1
2022-08-01 11:22:56 +10:00
Oliver
c5162c1947
Check if widget is mounted before calling setstate() (#193) 2022-07-29 20:01:06 +10:00
Oliver Walters
b7a37e50c5 Update version and release noes 2022-07-29 19:31:23 +10:00
Oliver
f652bebd83
Show loading overlay for "toggle star" operation (#192)
* Show loading overlay for "toggle star" operation

* Improve loading speed of PartDetail widget

* Improve loading of StockDetail widget
2022-07-29 19:30:22 +10:00
Oliver
e13817abed
New translations app_en.arb (Hungarian) (#191) 2022-07-29 19:29:50 +10:00
Oliver
dacbf880da
Improve error handling and reporting (#190)
* Prevent duplicate reporting of errors to sentry

* Prevent error message upload on some server error codes

* Filter out some common errors we are not interested in
2022-07-26 15:57:16 +10:00
Oliver
75e0a69eab
Automatically add a demo server profile (#189) 2022-07-25 14:26:26 +10:00
Oliver Walters
0165a4bad5 Bug fix for refreshable state
- Prevent setstate if widget is no longer mounted
2022-07-20 17:38:32 +10:00
Oliver
19ad3153e4
Remove and fix some TODO entries (#188) 2022-07-20 14:18:07 +10:00
Oliver
42d69365b8
New Crowdin updates (#187)
* New translations app_en.arb (French)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (German)
2022-07-20 13:02:58 +10:00
Oliver Walters
162d05aea5 Update release notes 2022-07-20 09:07:09 +10:00
Oliver
01dd046dd1
Display overlay screen for blocking operations (#186)
* Catch state error in homepage widget

* Add flutter_overlay_loader lib

- Displays an overlay screen to indicate blocking operation

* Wrap blocking widget transitions in a loading overlay

- Prevents user from doing other things while loading
- Shows the user that something is happening

* Linting fixes

* Show overlay when uploading attachment file

* Show overlay when downloading file also

* Show overlay when loading or submitting API forms

- Major improvements to usability "feel"

* UI improvements for stock item test results widget

* Fix API_FORM bug

- onSuccess function was not being called
2022-07-20 09:05:21 +10:00
Oliver
277193ecb0
Refactor create state (#184)
* Refactor createstate for api_form

- Refer to attributes of widget

* Refactor barcode state variables

* More udpates
2022-07-19 23:34:26 +10:00
Oliver
13ebaf43e1
List refactor (#179)
* Catch paginator bug if widget is disposed before request returns

* Refactoring paginated query widget

- Add option to enable / disable search filters

* Major refactor of paginated search widget

- Learned something new.. a state can access widget.<attribute>
- THIS CHANGES EVERTHING

* Preferences: Add code for tri-state values

- Also improve unit testing for preferences code

* Allow boolean form fields to be optionally tristate

* paginator: Allow custom boolean filters

* Remove outdated filtering preferences

* Refactor filter options

- Allow specification of more detailed options

* Add custom filters for "part" list

* filter tweaks

* Remove legacy "SublocationList" widget

* Add filtering option for locationlist

* Updates for stock location widget

* Refactor category display widget

* More widget refactoring

* Update main search widget

* Fix unit tests

* Improve filtering on BOM display page
2022-07-19 23:29:01 +10:00
Oliver
e03a8561b9
Main screen loading indicator (#183)
* Bug fix for login screen

- Prevent setState() from being called if the widget is no longer loaded

* Add callback function when API status changes

- Home screen uses this function to update connection status indicator

* Linting fixes
2022-07-19 23:10:06 +10:00
Oliver
7f3dfe7dd7
New Crowdin updates (#178)
* New translations app_en.arb (French)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Portuguese, Brazilian)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (German)

* New translations app_en.arb (German)
2022-07-19 22:06:10 +10:00
Oliver
33d11241e5
New translations app_en.arb (Portuguese) (#177) 2022-07-18 22:11:32 +10:00
Oliver
aa274b2e45
Stock location scan (#169)
* Add action for scanning a stock location into another location

* Adds barcode scan handler for new functionality

* Handle scanning of stock location

* Cleanup

* Refactor existing barcode scanning functions

- Will require extensive testing and validation

* Add entry to release notes

* Delete dead code

* Improved ordering based on stock quantity

* Bug fix for 'adjustStock' function

* Improve error responses for barcode scanning

* Improve error responses for barcode scanning

* Remove old debug statements

* Add some extra explanatory texts

* Icon change

* Fixes for unit tests

* Adds extra functionality for user profile manager

* Refactor barcode code - do not rely on BuildContext

* Adds initial unit testing for barcode scanning

- Work on mocking barcode data
- Add hooks for testing snackBar and audio files

* Linting fixes

* More barcode unit tests

* Cleanup unit tests for barcode

* Remove unused import

* Handle HTTPException in API

* Improvements for API unit testing

* Unit testing for scanning item into location

* Add unit test for scanning in items from a location context

* Unit test for scanning location into parent location

* Improve feedback for barcode scanning events
2022-07-18 22:10:00 +10:00
Oliver
c6678e201f
Fix broken android manifest (#172)
- Adds xmlns:tools to manifest xml file
- Ref: https://stackoverflow.com/questions/55334431/facing-below-error-toolsnode-associated-with-an-element-type-uses-permission
2022-07-14 20:31:44 +10:00
Oliver
acbb2d2947
Prevent specific permissions from being requested (#170)
open_file package requests the REQUEST_INSTALL_PACKAGES permission by default
2022-07-13 20:11:49 +10:00
Oliver
900074f458
New Crowdin updates (#168)
* New translations app_en.arb (Italian)

* New translations app_en.arb (Hungarian)
2022-07-12 11:41:06 +10:00
Oliver
cd0336696c
Merge pull request #165 from inventree/bump-version
Increment version and build numbers
2022-07-06 21:44:25 +10:00
Oliver Walters
38004228aa Increment version and build numbers 2022-07-06 21:38:48 +10:00
Oliver
6a42bc0ec0
Merge pull request #164 from inventree/list-ordering
List ordering
2022-07-06 21:36:53 +10:00
Oliver Walters
ed2523c3c5 Linting fixes 2022-07-06 21:29:26 +10:00
Oliver Walters
847fda7652 Enable ordering for purchase order list 2022-07-06 21:24:26 +10:00
Oliver Walters
2e7abf8a1e Allow "trailing" widget to be displayed on home screen entries 2022-07-06 21:21:19 +10:00
Oliver Walters
bb73fb7400 Cleanup 2022-07-06 21:17:52 +10:00
Oliver Walters
c3e6d3f902 Widget cleanup 2022-07-06 21:13:07 +10:00
Oliver Walters
a450154bac Add sorting options for the StockItem list 2022-07-06 21:07:19 +10:00
Oliver Walters
6192932322 Add ordering options for "Part" list 2022-07-06 20:59:38 +10:00
Oliver Walters
6d247f426c Further refactoring 2022-07-06 20:54:17 +10:00
Oliver Walters
7301243ed6 Major overhaul of "paginated list" widget class
- Simplify implementation
- Create mixin class for code reuse
- Allow custom app-bar
- Allow custom ordering / sorting options
- Improve code commenting / readability
2022-07-06 20:24:40 +10:00
Oliver Walters
979f950129 Update release notes 2022-07-06 09:51:05 +10:00
Oliver Walters
6c1099356f Enable basic ordering for BOM list 2022-07-06 09:49:40 +10:00
Oliver Walters
c878f37ec2 Implementing a generic "ordering" option configuration for paginated list widget 2022-07-05 21:42:55 +10:00
Oliver
9c4f6710ff
Merge pull request #162 from inventree/bom-display
Allows displays of Bill of Materials for assembled parts
2022-07-05 21:19:15 +10:00
Oliver Walters
78a5a9090d Adds new custom widget for displaying Bill of Materials data 2022-07-05 19:16:06 +10:00
Oliver Walters
591c6a5592 Update stock display to indicate allocations 2022-07-05 18:38:28 +10:00
Oliver Walters
62df40f4b3 Display "variants" in part detail view 2022-07-05 17:14:00 +10:00
Oliver Walters
e35c4df846 Allows displays of Bill of Materials for assembled parts 2022-07-05 16:59:32 +10:00
Oliver
f7e045aaeb
Update release.yml
Change labels / etc
2022-06-29 17:48:01 +10:00
Oliver
8eba549bb0
Merge pull request #160 from inventree/attachment-links
Adds ability to display "links" uploaded to attachment fields
2022-06-20 17:14:15 +10:00
Oliver Walters
0d06d07e0f Adds ability to display "links" uploaded to attachment fields 2022-06-20 15:32:36 +10:00
Oliver
447497f344
Merge pull request #159 from inventree/l10n_master
New Crowdin updates
2022-06-20 15:21:11 +10:00
Oliver
6463a06b35 New translations app_en.arb (German) 2022-06-19 23:23:00 +10:00
Oliver
ec94e3b1d3 New translations app_en.arb (Portuguese, Brazilian) 2022-06-19 11:21:19 +10:00
Oliver
a84f082bd6 New translations app_en.arb (Dutch) 2022-06-14 12:18:58 +10:00
Oliver
deb8666172 New translations app_en.arb (Dutch) 2022-06-14 00:23:42 +10:00
Oliver
184de1c01e New translations app_en.arb (Hungarian) 2022-06-12 04:35:29 +10:00
Oliver
46dd454c4a New translations app_en.arb (German) 2022-06-07 00:47:22 +10:00
Oliver
fe5587b3eb New translations app_en.arb (Hungarian) 2022-06-07 00:47:19 +10:00
Oliver
e7be7b4bf2 New translations app_en.arb (Italian) 2022-06-07 00:47:18 +10:00
Oliver
3db0f004c2 New translations app_en.arb (Japanese) 2022-06-07 00:47:17 +10:00
Oliver
bc85140264 New translations app_en.arb (Dutch) 2022-06-07 00:47:15 +10:00
Oliver
5a0e5d252d New translations app_en.arb (Polish) 2022-06-07 00:47:13 +10:00
Oliver
c534f4196d New translations app_en.arb (Turkish) 2022-06-07 00:47:10 +10:00
Oliver
d1bd8890c0 New translations app_en.arb (Portuguese) 2022-06-07 00:47:04 +10:00
Oliver
590036f082 New translations app_en.arb (French) 2022-06-07 00:47:03 +10:00
Oliver
6f13f429ce
Merge pull request #158 from inventree/string-fix
String fixes
2022-06-06 21:24:54 +10:00
Oliver Walters
4a695fa4ef String fixes 2022-06-06 21:15:58 +10:00
Oliver Walters
f0d91b12de Bump release version 2022-06-05 09:46:12 +10:00
Oliver Walters
bafa378c86 Merge remote-tracking branch 'origin/l10n_master' 2022-06-05 09:45:08 +10:00
Oliver
3d0afa3798 New translations app_en.arb (Hungarian) 2022-06-05 00:03:03 +10:00
Oliver
51a90f5fca
Merge pull request #155 from inventree/search-permissions
Wrap search queries in permission checks
2022-06-04 09:58:23 +10:00
Oliver Walters
2a685a743f Wrap search queries in permission checks 2022-06-04 09:31:34 +10:00
Oliver
62e6009aeb
Merge pull request #152 from inventree/more-attachments
More attachments
2022-06-04 00:40:58 +10:00
Oliver Walters
302532f5a4 Update release notes 2022-06-04 00:34:50 +10:00
Oliver Walters
0237e9c819 Adds attachment support for purchase orders 2022-06-04 00:28:39 +10:00
Oliver Walters
ada64f3971 Adds attachment support for the StockItem model 2022-06-04 00:23:10 +10:00
Oliver Walters
fcfda4ebff Display number of attachments on part view 2022-06-04 00:15:34 +10:00
Oliver Walters
10783cd1c4 Refactor PartAttachmentWidget
- Now displays generic attachments
- Augment the InvenTreeAttachment class to "know" about its model reference
2022-06-04 00:12:44 +10:00
Oliver
ffae280d31
Merge pull request #151 from inventree/unit-testing
Unit testing
2022-06-03 21:28:48 +10:00
Oliver Walters
b40a0f8dad Adds basic unit tests for part category 2022-06-03 21:23:22 +10:00
Oliver Walters
8ee10a3424 Adds more unit tests for the part model 2022-06-03 21:14:39 +10:00
Oliver Walters
7d1735b1b7 Fix import quotes 2022-06-03 20:57:08 +10:00
Oliver Walters
1769693687 Initial tests for grabbing model data from server 2022-06-03 20:55:17 +10:00
Oliver Walters
21e7a976ee Improve checks for API user permissions 2022-06-03 20:42:25 +10:00
Oliver
c8fa6bd992
Merge pull request #150 from inventree/silent-notifications
Pass URL through to the showServeError method
2022-06-03 20:29:29 +10:00
Oliver
aac215678b
Update README.md 2022-06-03 20:26:56 +10:00
Oliver Walters
dbc024491c Pass URL through to the showServeError method
- Can decide, based on the URL, if we want to show an error
2022-06-03 20:23:59 +10:00
Oliver
99462ffeb9
Merge pull request #149 from inventree/quarantine-stock-status
Add support for "quarantined" stock item status
2022-06-03 19:56:19 +10:00
Oliver Walters
aa4317a2fe Add support for "quarantined" stock item status 2022-06-03 19:51:08 +10:00
Oliver
9a5cf59efb
Merge pull request #148 from inventree/auto-release
Add template for auto-generation of release notes
2022-06-03 18:54:23 +10:00
Oliver Walters
83f157536b Add template for auto-generation of release notes 2022-06-03 18:49:36 +10:00
Oliver
702965c7ce
Merge pull request #143 from inventree/l10n_master
New Crowdin updates
2022-06-03 10:34:32 +10:00
Oliver
b26a6377ff New translations app_en.arb (Dutch) 2022-05-28 01:06:37 +10:00
Oliver
44bc20c55c New translations app_en.arb (Dutch) 2022-05-27 12:50:30 +10:00
Oliver
e0cb8512b1 New translations app_en.arb (German) 2022-05-26 12:39:36 +10:00
Oliver Walters
b6e77a5934 Merge branch '0.7.x' 2022-05-22 16:58:16 +10:00
Oliver Walters
6e93b9c7fa Increment build number 2022-05-22 16:57:52 +10:00
Oliver
7d24e1818f Merge pull request #138 from inventree/stacktrace-data
Attempt to capture stacktrace data when uploading a custom message to…

(cherry picked from commit 6f885d3a5c)
2022-05-22 16:52:13 +10:00
Oliver Walters
bbe56aba55 Bump version number 2022-05-22 16:44:11 +10:00
Oliver
bf722d6b76 Merge pull request #140 from inventree/legacy-api
Remove support for legacy stock transfer API code

(cherry picked from commit 333e5bb41d)
2022-05-22 16:43:48 +10:00
Oliver
333e5bb41d
Merge pull request #140 from inventree/legacy-api
Remove support for legacy stock transfer API code
2022-05-22 16:26:57 +10:00
Oliver Walters
1b901b39df Remove old stuffs 2022-05-22 16:19:10 +10:00
Oliver Walters
850c2b8c12 Remove support for legacy stock transfer API code
- Relies on modern API now
- Checks for error messages against hidden fields in stock items
2022-05-22 15:59:19 +10:00
Oliver
6f885d3a5c
Merge pull request #138 from inventree/stacktrace-data
Attempt to capture stacktrace data when uploading a custom message to…
2022-05-22 10:40:01 +10:00
Oliver Walters
69e00a5fd8 Attempt to capture stacktrace data when uploading a custom message to sentry 2022-05-22 10:23:27 +10:00
Oliver Walters
d796cd208d Merge branch 'master' into 0.7.x 2022-05-22 10:13:27 +10:00
Oliver Walters
9f7e0a8dbf Merge remote-tracking branch 'origin/l10n_master'
(cherry picked from commit 55f713e3aa)
2022-05-22 10:13:19 +10:00
Oliver Walters
55f713e3aa Merge remote-tracking branch 'origin/l10n_master' 2022-05-22 10:12:31 +10:00
Oliver
cfc9f09b80 Merge pull request #135 from inventree/unit-testing
Unit testing

(cherry picked from commit d55f594342)
2022-05-22 10:12:16 +10:00
Oliver
d55f594342
Merge pull request #135 from inventree/unit-testing
Unit testing
2022-05-22 10:11:36 +10:00
Oliver Walters
2cd73a98e8 Remove unused import 2022-05-22 10:03:56 +10:00
Oliver Walters
b98f044204 More checks 2022-05-22 09:56:22 +10:00
Oliver Walters
53b69d9623 Improvements for profile management 2022-05-22 09:29:04 +10:00
Oliver Walters
4e14bd077c Improved debug messages 2022-05-22 09:17:32 +10:00
Oliver Walters
625d29fcf1 Adds debug message helper 2022-05-22 09:13:49 +10:00
Oliver Walters
2e86a02343 more debug 2022-05-22 09:00:09 +10:00
Oliver Walters
8e1804b39d Debug 2022-05-22 08:48:46 +10:00
Oliver Walters
cdeac137bf Improved API connection testing 2022-05-22 08:38:34 +10:00
Oliver Walters
fc911ea5b5 Server connection check passes now 2022-05-22 08:27:01 +10:00
Oliver Walters
f13b04d029 Refactor audio file player
- Do not play if there is no context available (e.g. unit testing)
2022-05-22 08:23:20 +10:00
Oliver
c453aaaf8a New translations app_en.arb (Hungarian) 2022-05-22 06:39:34 +10:00
Oliver Walters
e424a3cf7b Start of unit tests for the actual API code 2022-05-22 00:04:13 +10:00
Oliver Walters
62b0fcbec5 Start InvenTree server 2022-05-21 21:03:43 +10:00
Oliver Walters
63dd081a1c Test fix? 2022-05-21 20:49:32 +10:00
Oliver Walters
253a75129a Extra tests 2022-05-21 20:41:03 +10:00
Oliver Walters
ee3b7502dc Skip some files 2022-05-21 20:30:00 +10:00
Oliver Walters
31325f4893 File name is apparently important... 2022-05-21 20:24:31 +10:00
Oliver Walters
12828e47f9 Had commented out the line that actually did anything... 2022-05-21 20:12:01 +10:00
Oliver Walters
6b0fd2a708 Add script to find and test all un-touched .dart files 2022-05-21 20:03:48 +10:00
Oliver Walters
b18dd92079 Fixes after preferences file refactor 2022-05-21 19:44:47 +10:00
Oliver Walters
df48450440 Use coveralls github action 2022-05-21 19:33:01 +10:00
Oliver Walters
a42c1dc886 Reduce CI actions 2022-05-21 19:30:23 +10:00
Oliver Walters
6ef95499b7 Add genhtml step 2022-05-21 19:27:44 +10:00
Oliver Walters
237a7da54a Add .coveragerc file 2022-05-21 00:44:07 +10:00
Oliver Walters
80d898e212 Add github token secret 2022-05-21 00:39:05 +10:00
Oliver Walters
b8d4130270 Adds tests for the "updateProfile" function 2022-05-21 00:30:56 +10:00
Oliver Walters
00baff7a97 All tests pass now 2022-05-21 00:26:09 +10:00
Oliver Walters
caa10b5f8f Add python requirements file 2022-05-20 23:48:58 +10:00
Oliver Walters
96ae1be3ec Add coveralls step 2022-05-20 23:42:30 +10:00
Oliver Walters
fe3c298f86 Workflow updates 2022-05-20 23:37:55 +10:00
Oliver Walters
d898efdf6d Rename github workflow 2022-05-20 23:35:20 +10:00
Oliver Walters
cd9af13e27 Adds an initial unit test 2022-05-20 23:34:02 +10:00
Oliver Walters
63a351bfad Merge remote-tracking branch 'origin/l10n_master' 2022-05-20 22:39:17 +10:00
Oliver
10b435f4fa Merge pull request #132 from inventree/search-fix
Search fix

(cherry picked from commit a4814816ad)
2022-05-20 22:38:49 +10:00
Oliver
8300cde3ec New translations app_en.arb (German) 2022-05-20 11:01:00 +10:00
Oliver
cb866fb45c New translations app_en.arb (French) 2022-05-20 11:00:58 +10:00
Oliver
a9f794af1f New translations app_en.arb (French) 2022-05-19 23:04:53 +10:00
Oliver
a4814816ad
Merge pull request #132 from inventree/search-fix
Search fix
2022-05-19 21:32:21 +10:00
Oliver Walters
11157b7c77 Fix search counter 2022-05-19 21:11:46 +10:00
Oliver Walters
941ee5e172 Prevent "old" search results from mucking up the displayed data
- Only accept results if the search term has not changed
2022-05-19 21:06:20 +10:00
Oliver Walters
acf89426ce Fix for search screen
- Change input and controller
- Add focus node
- Add "searching" indicator
2022-05-19 20:38:28 +10:00
Oliver Walters
d7d8cefddd v0.7.0 - Update version number 2022-05-17 20:28:26 +10:00
Oliver Walters
900a60aaa9 Merge remote-tracking branch 'origin/l10n_master' 2022-05-17 20:13:18 +10:00
Oliver
1b4c53b4b3 New translations app_en.arb (Hungarian) 2022-05-17 12:59:32 +10:00
Oliver
73a8e8da40
New translations app_en.arb (German) (#130) 2022-05-12 23:58:05 +10:00
Oliver
72e9162520
Merge pull request #129 from inventree/better-sentry
Add extra context information to sentry error reports
2022-05-12 23:11:32 +10:00
Oliver Walters
4d81cd0415 Update release notes 2022-05-12 22:48:46 +10:00
Oliver Walters
c90a849a5a Add extra context information to sentry error reports
- Should help to track down bugs where stacktrace is missing information
- Adds some more error catching, too
2022-05-12 22:46:12 +10:00
Oliver
e47d88a4bb
Merge pull request #128 from inventree/sentry-improvements
Sentry improvements
2022-05-11 00:11:47 +10:00
Oliver Walters
b4e8d47d9a Prevent notification requests if server is not connected 2022-05-10 23:49:03 +10:00
Oliver Walters
23a27fde67 Adds ability to show / hide password in profile widget 2022-05-10 23:42:27 +10:00
Oliver Walters
6d764e32a0 Improve barcode error handling 2022-05-10 23:37:21 +10:00
Oliver Walters
e2ed80f2d0 Add barcode scan context 2022-05-10 18:26:55 +10:00
Oliver Walters
610f6533d0 Refactor sentry error reporting for model class 2022-05-10 18:16:15 +10:00
Oliver Walters
7c36659fe9 Remove "submit feedback" widget
- Now open source on github!
2022-05-10 18:15:53 +10:00
Oliver
f2af364624
Merge pull request #125 from inventree/hide-stuff
Hide manufacturer / supplier / customer screens
2022-05-10 17:46:52 +10:00
Oliver Walters
4bd800d273 linting fix 2022-05-10 17:07:53 +10:00
Oliver Walters
969875ad49 Hide manufacturer / supplier / customer screens
- For now, as they don't do anything useful
- Will add back in once features are expanded
2022-05-10 08:42:08 +10:00
Oliver
e8bb56ef3f
Merge pull request #124 from inventree/identify
Adds support for "locate" plugin
2022-05-10 08:28:27 +10:00
Oliver Walters
bc53dafaba Add plugin support status to server information screen 2022-05-10 08:12:53 +10:00
Oliver Walters
9f6269375f linting 2022-05-10 01:19:38 +10:00
Oliver Walters
366c732668 Add support for locating a stock item 2022-05-10 00:30:28 +10:00
Oliver Walters
5ba887d59b Refactor locate function 2022-05-10 00:28:49 +10:00
Oliver Walters
03c6de8255 Stock location can now be identified via the app 2022-05-10 00:15:20 +10:00
Oliver Walters
30b4ed9600 Update release notes 2022-05-09 22:21:26 +10:00
Oliver
cc3f7e7f7c
Merge pull request #123 from inventree/server-settings
Server settings
2022-05-09 22:20:09 +10:00
Oliver Walters
97ee077419 Require API version 46 2022-05-09 21:46:29 +10:00
Oliver Walters
65570eec33 Adds code for requesting user settings 2022-05-09 21:42:46 +10:00
Oliver Walters
059b69ce99 Adds code to requeest global setting from server
- Settings are individually cached for 5 minutes
- For now, use it for the purchase order reference prefix
2022-05-09 21:41:34 +10:00
Oliver
1579278c8f
Merge pull request #122 from inventree/oobe
Display a "launch" screen if server is not connected
2022-05-09 20:53:39 +10:00
Oliver Walters
da3b668e8c Adds class representing global and user settings 2022-05-09 20:53:12 +10:00
Oliver Walters
349eca4533 Save datetime since last reload 2022-05-09 20:51:27 +10:00
Oliver Walters
347d2175be Display a "launch" screen if server is not connected 2022-05-09 20:37:57 +10:00
Oliver
97b4eefc13
Merge pull request #120 from inventree/units-fix
Fix duplicate display of units
2022-05-07 15:38:36 +10:00
Oliver Walters
0140ffd96f Fix duplicate display of units 2022-05-07 15:25:26 +10:00
Oliver Walters
a37551a45b Merge remote-tracking branch 'origin/l10n_master'
# Conflicts:
#	lib/l10n/hu_HU/app_hu_HU.arb
#	lib/l10n/nl_NL/app_nl_NL.arb
2022-05-07 15:20:17 +10:00
Oliver
52b3873cd7 New translations app_en.arb (Hungarian) 2022-05-05 03:26:48 +10:00
Oliver
04ca7cc783 New translations app_en.arb (German) 2022-05-04 15:28:20 +10:00
Oliver
0c025289ad New translations app_en.arb (Hungarian) 2022-05-04 15:28:18 +10:00
Oliver
40730af102 New translations app_en.arb (Japanese) 2022-05-04 15:28:16 +10:00
Oliver
27167f93b3 New translations app_en.arb (Dutch) 2022-05-04 15:28:14 +10:00
Oliver
4e7930f8df New translations app_en.arb (Polish) 2022-05-04 15:28:12 +10:00
Oliver
943669e67f New translations app_en.arb (Turkish) 2022-05-04 15:28:10 +10:00
Oliver
7ff61add76 New translations app_en.arb (French) 2022-05-04 15:28:04 +10:00
Oliver Walters
23706a19bc Update release notes 2022-05-04 12:48:51 +10:00
Oliver
1750f93720
Merge pull request #119 from inventree/notifications
Notifications
2022-05-04 12:46:38 +10:00
Oliver Walters
407250c336 Further linting fixes 2022-05-04 12:17:55 +10:00
Oliver Walters
7ef7096e26 Updates to search controller 2022-05-04 11:53:11 +10:00
Oliver Walters
6533cc4af6 Display badge showing current number of unread notifications 2022-05-04 11:41:53 +10:00
Oliver Walters
a36a251f23 Remove debug message 2022-05-04 11:26:47 +10:00
Oliver Walters
020f006410 Adds ability to "dismiss" a notification 2022-05-04 11:21:52 +10:00
Oliver Walters
6bbae67482 Add model for notifications
- Display a list of active notifications
2022-05-04 11:11:29 +10:00
Oliver Walters
b8857f2dbe Adds skeleton widget for displayign notifications 2022-05-04 10:50:51 +10:00
Oliver Walters
a3597c5d61 Allow search widget to be constructed with or without an app bar 2022-05-04 10:42:33 +10:00
Oliver Walters
b6a5af08d8 Fix title for stock items list 2022-05-04 10:29:44 +10:00
Oliver Walters
3fa68ec6da The "search" window is now a tab on the main screen 2022-05-04 10:27:04 +10:00
Oliver Walters
102b4e021b Change home screen from "grid" display to "list" display 2022-05-04 10:02:50 +10:00
Oliver Walters
1e1ed96d64 0.6.2
- Update release notes
2022-05-03 09:38:45 +10:00
Oliver
15dba61300 New translations app_en.arb (Hungarian) 2022-05-03 00:18:02 +10:00
Oliver
820681364c
Merge pull request #116 from inventree/translation-improvements
Translation improvements
2022-05-02 13:43:23 +10:00
Oliver Walters
501971681e Adds missing step from android CI build 2022-05-02 13:28:47 +10:00
Oliver Walters
69bbc3bec3 remove const keyword 2022-05-02 13:20:37 +10:00
Oliver Walters
95c3bc5ae5 Fix delegate issue 2022-05-02 13:14:17 +10:00
Oliver Walters
71c16e0556 Auto generate a list of supported locales whenever translations are updated 2022-05-02 13:07:04 +10:00
Oliver Walters
5e4abcf68c Translation script creates fallback locale files 2022-05-02 12:49:59 +10:00
Oliver
26e48f57eb
New Crowdin updates (#115)
* New translations app_en.arb (French)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Spanish)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Greek)

* New translations app_en.arb (German)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Thai)
2022-05-02 12:23:27 +10:00
Oliver
9043ae3bf2 New translations app_en.arb (Thai) 2022-05-02 12:22:07 +10:00
Oliver
9ed7a9a224 New translations app_en.arb (Czech) 2022-05-02 12:22:06 +10:00
Oliver
62be205ab0 New translations app_en.arb (German) 2022-05-02 12:22:06 +10:00
Oliver
570daff7ff New translations app_en.arb (Greek) 2022-05-02 12:22:05 +10:00
Oliver
892ad353f4 New translations app_en.arb (Hebrew) 2022-05-02 12:22:04 +10:00
Oliver
4f4c8ad556 New translations app_en.arb (Hungarian) 2022-05-02 12:22:04 +10:00
Oliver
e61949d129 New translations app_en.arb (Italian) 2022-05-02 12:22:03 +10:00
Oliver
95a9c21014 New translations app_en.arb (Japanese) 2022-05-02 12:22:01 +10:00
Oliver
c4e90252e0 New translations app_en.arb (Korean) 2022-05-02 12:22:01 +10:00
Oliver
d571e25bc3 New translations app_en.arb (Dutch) 2022-05-02 12:22:00 +10:00
Oliver
1585da0b6f New translations app_en.arb (Norwegian) 2022-05-02 12:21:59 +10:00
Oliver
e661f6d607 New translations app_en.arb (Spanish) 2022-05-02 12:21:58 +10:00
Oliver
831fe8d61d New translations app_en.arb (Polish) 2022-05-02 12:21:58 +10:00
Oliver
09565bd462 New translations app_en.arb (Russian) 2022-05-02 12:21:56 +10:00
Oliver
546af96792 New translations app_en.arb (Swedish) 2022-05-02 12:21:56 +10:00
Oliver
587ea41930 New translations app_en.arb (Turkish) 2022-05-02 12:21:55 +10:00
Oliver
b9ced1b03b New translations app_en.arb (Chinese Simplified) 2022-05-02 12:21:54 +10:00
Oliver
12b2c7f445 New translations app_en.arb (Vietnamese) 2022-05-02 12:21:53 +10:00
Oliver
26dd45eca3 New translations app_en.arb (Indonesian) 2022-05-02 12:21:52 +10:00
Oliver
7ca05112dd New translations app_en.arb (Persian) 2022-05-02 12:21:51 +10:00
Oliver
e13e2de7dd New translations app_en.arb (Portuguese) 2022-05-02 12:21:50 +10:00
Oliver
eac9da3bb7 New translations app_en.arb (French) 2022-05-02 12:21:49 +10:00
Oliver
603e2e24ca
Merge pull request #113 from inventree/l10n_master
New Crowdin updates
2022-05-02 12:16:36 +10:00
Oliver Walters
b6273287a9 Update CI workflow scripts 2022-05-02 12:08:51 +10:00
Oliver Walters
f780023770 Reintroduce translation file collection script 2022-05-02 12:05:52 +10:00
Oliver Walters
a6dafd2566 Copy across previous translation flies 2022-05-02 12:02:22 +10:00
Oliver
a7b58f0ad6 New translations app_en.arb (Thai) 2022-05-02 11:54:27 +10:00
Oliver
c08b07b174 New translations app_en.arb (Czech) 2022-05-02 11:54:26 +10:00
Oliver
0d8ed43e47 New translations app_en.arb (German) 2022-05-02 11:54:26 +10:00
Oliver
fad347236e New translations app_en.arb (Greek) 2022-05-02 11:54:25 +10:00
Oliver
8bd9dd3dcf New translations app_en.arb (Hebrew) 2022-05-02 11:54:25 +10:00
Oliver
177750a419 New translations app_en.arb (Hungarian) 2022-05-02 11:54:24 +10:00
Oliver
fadbb9769d New translations app_en.arb (Italian) 2022-05-02 11:54:23 +10:00
Oliver
bae1a47521 New translations app_en.arb (Japanese) 2022-05-02 11:54:23 +10:00
Oliver
9429741e12 New translations app_en.arb (Korean) 2022-05-02 11:54:22 +10:00
Oliver
73f90458f5 New translations app_en.arb (Dutch) 2022-05-02 11:54:21 +10:00
Oliver
591486cdd2 New translations app_en.arb (Norwegian) 2022-05-02 11:54:21 +10:00
Oliver
fa9957de0c New translations app_en.arb (Spanish) 2022-05-02 11:54:20 +10:00
Oliver
0a49632f11 New translations app_en.arb (Polish) 2022-05-02 11:54:20 +10:00
Oliver
78af823a85 New translations app_en.arb (Russian) 2022-05-02 11:54:19 +10:00
Oliver
cc897cea79 New translations app_en.arb (Swedish) 2022-05-02 11:54:18 +10:00
Oliver
f0f604c586 New translations app_en.arb (Turkish) 2022-05-02 11:54:18 +10:00
Oliver
ccfc5bc91b New translations app_en.arb (Chinese Simplified) 2022-05-02 11:54:17 +10:00
Oliver
7f12464419 New translations app_en.arb (Vietnamese) 2022-05-02 11:54:17 +10:00
Oliver
7bb6e872b9 New translations app_en.arb (Portuguese, Brazilian) 2022-05-02 11:54:16 +10:00
Oliver
c12bf5b269 New translations app_en.arb (Indonesian) 2022-05-02 11:54:15 +10:00
Oliver
a6f51ede67 New translations app_en.arb (Persian) 2022-05-02 11:54:15 +10:00
Oliver
10597cc6ed New translations app_en.arb (Spanish, Mexico) 2022-05-02 11:54:14 +10:00
Oliver
fba412ddba New translations app_en.arb (Portuguese) 2022-05-02 11:54:14 +10:00
Oliver
e99e70f89e New translations app_en.arb (French) 2022-05-02 11:54:13 +10:00
Oliver
a08e98493a
Update crowdin.yml 2022-05-02 11:52:35 +10:00
Oliver
2eb71194ba
Update crowdin.yml 2022-05-02 11:50:07 +10:00
Oliver
9b9b2b52b6
Update crowdin.yml 2022-05-02 11:42:35 +10:00
Oliver
f084097b9d
Merge pull request #108 from inventree/translation-recovery
Simplify translation scheme
2022-05-02 11:22:29 +10:00
Oliver Walters
5b095be5f8 Simplify translation scheme
Recover old translation data
2022-05-02 11:06:50 +10:00
Oliver
e28beb6a5e
New Crowdin updates (#107)
* New translations app_en.arb (French)

* New translations app_en.arb (Portuguese)

* New translations app_en.arb (Spanish, Mexico)

* New translations app_en.arb (Persian)

* New translations app_en.arb (Indonesian)

* New translations app_en.arb (Vietnamese)

* New translations app_en.arb (Chinese Simplified)

* New translations app_en.arb (Turkish)

* New translations app_en.arb (Swedish)

* New translations app_en.arb (Russian)

* New translations app_en.arb (Polish)

* New translations app_en.arb (Norwegian)

* New translations app_en.arb (Dutch)

* New translations app_en.arb (Korean)

* New translations app_en.arb (Japanese)

* New translations app_en.arb (Italian)

* New translations app_en.arb (Hungarian)

* New translations app_en.arb (Hebrew)

* New translations app_en.arb (Greek)

* New translations app_en.arb (German)

* New translations app_en.arb (Czech)

* New translations app_en.arb (Thai)
2022-05-02 11:00:29 +10:00
Oliver
4db6418898 Update Crowdin configuration file 2022-05-02 10:57:47 +10:00
Oliver
c5e65973cb
Merge pull request #106 from inventree/translation-integration
Translation integration
2022-05-02 10:53:50 +10:00
Oliver Walters
2cdf927d98 linting fix 2022-05-02 10:36:03 +10:00
Oliver Walters
ef422b4f00 Ensure output directory has been created 2022-05-02 10:29:53 +10:00
Oliver Walters
a5a78f21cd Collect translation files as part of CI 2022-05-02 10:16:34 +10:00
Oliver Walters
245f18be6e Update build instructions 2022-05-02 10:04:30 +10:00
Oliver Walters
56b11b9956 Add readme file 2022-05-02 10:03:37 +10:00
Oliver Walters
77b24ad9e4 Change path to translations 2022-05-02 09:59:08 +10:00
Oliver Walters
1f75fb4c92 Add latest translation files 2022-05-02 09:56:58 +10:00
Oliver Walters
d92e80ce92 Remove translation repo submodule 2022-05-02 09:46:07 +10:00
214 changed files with 71341 additions and 9519 deletions

3
.fvmrc Normal file
View file

@ -0,0 +1,3 @@
{
"flutter": "3.32.4"
}

32
.github/release.yml vendored Normal file
View file

@ -0,0 +1,32 @@
# .github/release.yml
changelog:
exclude:
labels:
- wontfix
- translation
categories:
- title: Breaking Changes
labels:
- Semver-Major
- breaking
- title: Security Patches
labels:
- security
- title: New Features
labels:
- Semver-Minor
- enhancement
- title: Bug Fixes
labels:
- Semver-Patch
- bug
- title: Devops / Setup Changes
labels:
- docker
- setup
- demo
- CI
- title: Other Changes
labels:
- "*"

View file

@ -3,10 +3,10 @@
name: Android name: Android
on: on:
push: pull_request:
branches: branches:
- master - master
pull_request: push:
branches: branches:
- master - master
@ -17,23 +17,43 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
submodules: recursive
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v1 uses: actions/setup-java@v3
with: with:
java-version: '12.x' distribution: 'temurin'
- name: Setup Flutter java-version: '17'
uses: subosito/flutter-action@v1
- name: Setup FVM
id: fvm-config-action
uses: kuhnroyal/flutter-fvm-config-action@v2
- uses: subosito/flutter-action@v2
with: with:
flutter-version: '2.10.3' flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
cache: false
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
pub-cache-key: "flutter-pub:os:-:channel:-:version:-:arch:-:hash:"
pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
- run: flutter --version
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2.4.2
with: with:
gradle-version: 6.1.1 gradle-version: 8.7
- name: Collect Translation Files
run: |
cd lib/l10n
python3 collect_translations.py
- name: Build for Android - name: Build for Android
run: | run: |
flutter pub get dart pub global activate fvm
cp lib/dummy_dsn.dart lib/dsn.dart fvm install
flutter build apk --debug fvm flutter pub get
fvm flutter build apk --debug

93
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,93 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_SITE_URL: http://localhost:8000
INVENTREE_DB_ENGINE: django.db.backends.sqlite3
INVENTREE_DB_NAME: ../inventree_unit_test_db.sqlite3
INVENTREE_MEDIA_ROOT: ../test_inventree_media
INVENTREE_STATIC_ROOT: ../test_inventree_static
INVENTREE_BACKUP_DIR: ../test_inventree_backup
INVENTREE_ADMIN_USER: testuser
INVENTREE_ADMIN_PASSWORD: testpassword
INVENTREE_ADMIN_EMAIL: test@test.com
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Flutter and FVM
id: fvm-config-action
uses: kuhnroyal/flutter-fvm-config-action@v2
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
cache: true
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
pub-cache-key: "flutter-pub:os:-:channel:-:version:-:arch:-:hash:"
pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
- name: Collect Translation Files
run: |
cd lib/l10n
python collect_translations.py
- name: Static Analysis Tests
working-directory: .
run: |
python ./find_dart_files.py
dart pub global activate fvm
fvm install
fvm flutter pub get
fvm flutter analyze
dart format --output=none --set-exit-if-changed .
- name: Start InvenTree Server
run: |
sudo apt-get install python3-dev python3-pip python3-venv python3-wheel g++
pip3 install invoke
git clone --depth 1 https://github.com/inventree/inventree ./inventree_server
cd inventree_server
invoke install
invoke migrate
invoke dev.import-fixtures
invoke dev.server -a 127.0.0.1:8000 &
invoke wait
sleep 30
- name: Unit Tests
run: |
fvm flutter test --coverage
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -3,10 +3,10 @@
name: iOS name: iOS
on: on:
push: pull_request:
branches: branches:
- master - master
pull_request: push:
branches: branches:
- master - master
@ -17,23 +17,44 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v1 uses: actions/setup-java@v3
with: with:
java-version: '12.x' distribution: 'temurin'
java-version: '11'
- name: Setup FVM
id: fvm-config-action
uses: kuhnroyal/flutter-fvm-config-action@v2
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v1 uses: subosito/flutter-action@v2
with: with:
flutter-version: '2.10.3' flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}
cache: false
cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:"
cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
pub-cache-key: "flutter-pub:os:-:channel:-:version:-:arch:-:hash:"
pub-cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:"
- name: Collect Translation Files
run: |
cd lib/l10n
python3 collect_translations.py
- name: Build for iOS - name: Build for iOS
run: | run: |
flutter pub get dart pub global activate fvm
fvm install
fvm flutter pub get
fvm flutter precache --ios
cd ios cd ios
pod repo update pod repo update
pod install pod install
cd .. cd ..
cp lib/dummy_dsn.dart lib/dsn.dart fvm flutter build ios --release --no-codesign --no-tree-shake-icons
flutter build ios --release --no-codesign

View file

@ -1,37 +0,0 @@
# Run flutter linting checks
name: lint
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
lint:
runs-on: ubuntu-latest
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Setup Java
uses: actions/setup-java@v1
with:
java-version: '12.x'
- name: Setup Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: '2.10.3'
- run: flutter pub get
- run: cp lib/dummy_dsn.dart lib/dsn.dart
- run: flutter analyze
- run: flutter test --coverage

8
.gitignore vendored
View file

@ -11,8 +11,9 @@
coverage/* coverage/*
# Sentry API key # This file is auto-generated as part of the CI process
lib/dsn.dart test/coverage_helper_test.dart
InvenTreeSettings.db
# App signing key # App signing key
android/key.properties android/key.properties
@ -81,3 +82,6 @@ ios/Podfile.lock
!**/ios/**/default.pbxuser !**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3 !**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
# FVM Version Cache
.fvm/

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "lib/l10n"]
path = lib/l10n
url = git@github.com:inventree/inventree-app-i18n.git

8
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,8 @@
repos:
- repo: local
hooks:
- id: dart-format
name: Dart Format
entry: dart format
language: system
types: [dart]

132
BUILDING.md Normal file
View file

@ -0,0 +1,132 @@
## InvenTree App Development
For developers looking to contribute to the project, we use Flutter for app development. The project has been tested in Android Studio (on both Windows and Mac) and also VSCode.
## Prerequisites
To build the app from source, you will need the following tools installed on your system:
- Android Studio or Visual Studio Code (with Flutter and Dart plugins)
- [Flutter Version Management (FVM)](https://fvm.app/) - We use FVM to manage Flutter versions
### iOS Development
For iOS development, you will need a Mac system with XCode installed.
### Java Version
Some versions of Android Studio ship with a built-in version of the Java JDK. However, the InvenTree app requires [JDK 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) to be installed.
If you see any errors related to JDK version mismatch, download and install the correct version of the JDK (from the link above) and update your Android Studio settings to point to the correct JDK location:
```bash
fvm flutter config --jdk-dir /path/to/jdk
```
## Invoke Tasks
We use the [invoke](https://www.pyinvoke.org) to run some core tasks - you will need python and invoke installed on your local system.
## Flutter Version Management (FVM)
This project uses [Flutter Version Management (FVM)](https://fvm.app/) to ensure consistent Flutter versions across development environments and CI/CD pipelines.
For installation instructions, please refer to the [official FVM documentation](https://fvm.app/documentation/getting-started/installation).
Once installed, FVM will automatically use the Flutter version specified in the `.fvmrc` file at the root of the project.
### Visual Studio Code
To set up Visual Studio Code, you will need to make sure the `.vscode` directory exists. Then run `fvm use` to ensure the correct Flutter version is used.
```
mkdir -p .vscode
fvm use
```
#### What happens:
- Downloads SDK if not cached
- Creates `.fvm` directory with SDK symlink
- Updates `.fvmrc` configuration
- Configures IDE settings
- Runs `flutter pub get`
### Android Studio
To set up Android Studio, run `fvm use` to ensure the correct Flutter version is used.
```
fvm use
```
#### What happens:
- Downloads SDK if not cached
- Creates `.fvm` directory with SDK symlink
- Updates `.fvmrc` configuration
- Runs `flutter pub get`
Set Flutter SDK path in Android Studio:
1. Open Android Studio
2. Go to `File` -> `Settings` -> `Languages & Frameworks` -> `Flutter`
3. Set `Flutter SDK path` to `.fvm/flutter_sdk`:
![Setting Flutter SDK path in Android Studio](docs/android_studio_fvm.png)
## Getting Started
Initial project setup (after you have installed all required dev tools) is as follows:
Generate initial translation files:
```
invoke translate
```
Install required flutter packages:
```
fvm flutter pub get
```
You should now be ready to debug on a connected or emulated device!
## Troubleshooting
### Flutter Doctor
If you're experiencing issues with the development environment, run Flutter Doctor to diagnose problems:
```bash
fvm flutter doctor -v
```
This will check your Flutter installation and identify any issues with your setup. Common issues include:
- Missing Android SDK components
- iOS development tools not properly configured
- Missing dependencies
Fix any identified issues before proceeding with development.
## Building Release Versions
Building release versions for target platforms (either android or iOS) is simplified using invoke:
### Android
Build Android release:
```
invoke android
```
### iOS
Build iOS release:
```
invoke ios
```

50
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,50 @@
# Contributing to InvenTree App
Thank you for considering contributing to the InvenTree App! This document outlines some guidelines to ensure smooth collaboration.
## Code Style and Formatting
### Dart Formatting
We enforce consistent code formatting using Dart's built-in formatter. Before submitting a pull request:
1. Run the formatter on your code:
```bash
fvm dart format .
```
2. Our CI pipeline will verify that all code follows the standard Flutter/Dart formatting rules. Pull requests with improper formatting will fail CI checks.
### General Guidelines
- Write clear, readable, and maintainable code
- Include comments where necessary
- Follow Flutter/Dart best practices
- Write tests for new features when applicable
## Pull Request Process
1. Fork the repository and create a feature branch
2. Make your changes
3. Ensure your code passes all tests and linting
4. Format your code using `invoke format`
5. Submit a pull request with a clear description of the changes
6. Address any review comments
## Development Setup
1. Ensure you have Flutter installed (we use Flutter Version Management)
2. Check the required Flutter version in the `.fvmrc` file
3. Install dependencies with `fvm flutter pub get`
4. Run tests with `fvm flutter test`
## Reporting Issues
When reporting issues, please include:
- Clear steps to reproduce the issue
- Expected behavior
- Actual behavior
- Screenshots if applicable
- Device/environment information
Thank you for contributing to the InvenTree App!

View file

@ -3,11 +3,36 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![Android](https://github.com/inventree/inventree-app/actions/workflows/android.yaml/badge.svg) ![Android](https://github.com/inventree/inventree-app/actions/workflows/android.yaml/badge.svg)
![iOS](https://github.com/inventree/inventree-app/actions/workflows/ios.yaml/badge.svg) ![iOS](https://github.com/inventree/inventree-app/actions/workflows/ios.yaml/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/inventree/inventree-app/badge.svg?branch=master)](https://coveralls.io/github/inventree/inventree-app?branch=master)
The InvenTree mobile / tablet application is a companion app for the [InvenTree stock management system](https://github.com/inventree/InvenTree). The InvenTree mobile / tablet application is a companion app for the [InvenTree stock management system](https://github.com/inventree/InvenTree).
Written in the [Flutter](https://flutter.dev/) environment, the app provides native support for Android and iOS devices. Written in the [Flutter](https://flutter.dev/) environment, the app provides native support for Android and iOS devices.
<p align="center">
<img width="30%" src="https://github.com/user-attachments/assets/aee96f90-2953-47f6-916a-06f19d3b8aa5">
</p>
## Installation
You can install the app via the following channels:
### Google Play Store (Android)
Download and install from the [Google Play Store](https://play.google.com/store/apps/details?id=inventree.inventree_app&hl=en_AU)
### Apple Store (iOS)
Download and install from the [Apple App Store](https://apps.apple.com/au/app/inventree/id1581731101)
### Direct Download (Android)
We provide direct downloads for Android users - view our [download page via polar.sh](https://polar.sh/inventree/products/299bf0d5-af88-4e0f-becf-c007ad37ecf2)
## User Documentation ## User Documentation
User documentation for the InvenTree mobile app can be found [within the InvenTree documentation](https://inventree.readthedocs.io/en/latest/app/app/). User documentation for the InvenTree mobile app can be found [within the InvenTree documentation](https://inventree.readthedocs.io/en/latest/app/app/).
## Developer Documentation
Refer to the [build instructions](BUILDING.md) for information on how to build the app from source.

View file

@ -1,47 +0,0 @@
# Release Process
## Android Play Store
[Reference](https://flutter.dev/docs/deployment/android#signing-the-app)
### Key File
Add a file `key.properties` under the android/ directory
### Increment Build Number
Make sure that the build number is incremented every time (or it will be rejected by Play Store).
### Copy Translations
Ensure that the translation files have been updated, and copied into the correct directory!!
```
cd lib/l10n
python update_translations.py
```
### Build Appbundle
`flutter build appbundle`
### Upload Appbundle
Upload the appbundle file to the Android developer website.
## Apple Store
Ref: https://flutter.dev/docs/deployment/ios
### Build ipa
```
flutter clean
flutter build ipa
```
### Validate and Distribute
- Open `./build/ios/archive/Runner.xcarchive` in Xcode
- Run "Validate App"
- Run "Distribute App"

View file

@ -6,8 +6,6 @@ analyzer:
- lib/generated/** - lib/generated/**
language: language:
strict-raw-types: true strict-raw-types: true
strong-mode:
implicit-casts: false
linter: linter:
rules: rules:
@ -21,6 +19,8 @@ linter:
prefer_double_quotes: true prefer_double_quotes: true
unreachable_from_main: false
prefer_final_locals: false prefer_final_locals: false
prefer_const_constructors: false prefer_const_constructors: false
@ -74,4 +74,12 @@ linter:
avoid_dynamic_calls: false avoid_dynamic_calls: false
avoid_classes_with_only_static_members: false avoid_classes_with_only_static_members: false
no_leading_underscores_for_local_identifiers: false
use_super_parameters: false
# TODO: Enable unnecessary_async and unawaited_futures rules
unnecessary_async: false

14
android/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View file

@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@ -21,10 +22,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
@ -32,7 +29,18 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 31 namespace "inventree.inventree_app"
compileSdkVersion 35
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
// If using Kotlin
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
@ -48,8 +56,8 @@ android {
defaultConfig { defaultConfig {
applicationId "inventree.inventree_app" applicationId "inventree.inventree_app"
minSdkVersion 25 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 35
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@ -83,7 +91,6 @@ dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support:multidex:2.0.1' androidTestImplementation 'com.android.support:multidex:2.0.1'
implementation "androidx.core:core:1.5.0-rc01" implementation "androidx.core:core:1.9.0"
implementation 'androidx.appcompat:appcompat:1.0.0' implementation 'androidx.appcompat:appcompat:1.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
} }

View file

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="inventree.inventree_app"> package="inventree.inventree_app">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that <!-- io.flutter.app.FlutterApplication is an android.app.Application that
@ -29,10 +31,6 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- until Flutter renders its first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<!-- Theme to apply as soon as Flutter begins rendering frames --> <!-- Theme to apply as soon as Flutter begins rendering frames -->
<meta-data <meta-data
@ -53,7 +51,12 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.MICROPHONE"/> <uses-permission android:name="android.permission.MICROPHONE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--
Prevent lower level dependencies from including specific permissions.
Ref: https://developer.android.com/studio/build/manage-manifests
-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES" tools:node="remove"/>
</manifest> </manifest>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowSplashScreenBackground">@color/splash_screen_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/launch_background</item>
</style>
</resources>

View file

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="inventree.inventree_app">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->

View file

@ -1,22 +1,8 @@
buildscript {
ext.kotlin_version = '1.5.10'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
} }
@ -29,6 +15,6 @@ subprojects {
} }
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View file

@ -1,4 +1,8 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.daemon=true
android.enableR8=true org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx4096M
android.enableD8=true
android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true

View file

@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
networkTimeout=30000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

View file

@ -1,15 +1,25 @@
include ':app' pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
def plugins = new Properties() repositories {
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') google()
if (pluginsFile.exists()) { mavenCentral()
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } gradlePluginPortal()
}
} }
plugins.each { name, path -> plugins {
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() id "dev.flutter.flutter-plugin-loader" version "1.0.0"
include ":$name" id "com.android.application" version "8.6.0" apply false
project(":$name").projectDir = pluginDirectory id "org.jetbrains.kotlin.android" version "1.9.25" apply false
} }
include ":app"

View file

@ -1,6 +1,23 @@
## InvenTree App Credits ## Contributors
---
### Sound Files Thanks to the following contributors, for their work building this app!
- Some sound files have been sourced from [https://www.zapsplat.com](https://www.zapsplat.com) - [SchrodingersGat](https://github.com/SchrodingersGat) (*Lead Developer*)
- [cbenhagen](https://github.com/cbenhagen)
- [Guusggg](https://github.com/Guusggg)
- [GoryMoon](https://github.com/GoryMoon)
- [simonkuehling](https://github.com/simonkuehling)
- [Bobbe](https://github.com/30350n)
- [awnz](https://github.com/awnz)
- [joaomnuno](https://github.com/joaomnuno)
- [Alex9779](https://github.com/Alex9779)
--------
## Assets
The InvenTree App makes use of the following third party assets
- Icons are provided by [tabler.io](https://tabler.io/icons)
- Sound files have been sourced from [zapsplat](https://www.zapsplat.com)
--------

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View file

@ -1,6 +1,468 @@
## InvenTree App Release Notes ### x.xx.x - Month Year
--- ---
- Support display of custom status codes
- Fix default values for list sorting
### 0.21.2 - January 2026
---
- Fixes bug which launched camera twice when uploading an attachment
- Fixed bug related to list sorting and filtering
### 0.21.1 - November 2025
---
- Fixed app freeze bug after form submission
### 0.21.0 - November 2025
---
- Support label printing again, fixing issues with new printing API
- Adds zoom controller for barcode scanner camera view
- Display default stock location in Part detail page
- Display stock information in SupplierPart detail page
### 0.20.2 - November 2025
---
- Fixes URL for reporting issues on GitHub
- Fix for uploading files against server with self-signed certificates
### 0.20.1 - October 2025
---
- Bug fix for camera barcode scanner
### 0.20.0 - October 2025
---
- View pending shipments from the home screen
- Display detail view for shipments
- Adds ability to ship pending outgoing shipments
- Adds ability to mark outgoing shipments as "checked" or "unchecked"
- Updated translations
### 0.19.3 - September 2025
---
- Fixes incorrect priority of barcode scanner results
### 0.19.2 - August 2025
---
- Allow purchase orders to be completed
- Improved UX across the entire app
- Fix bug which prevented display of part images for purchase order line items
### 0.19.1 - July 2025
---
- Fixes bug related to barcode scanning with certain devices
### 0.19.0 - June 2025
---
- Replace barcode scanning library for better performance
- Display part pricing information
- Updated theme support
- Fix broken documentation link
- Reduce frequency of notification checks
- Updated translations
- Add image cropping functionality
### 0.18.1 - April 2025
---
- Fix bug associated with handling invalid URLs
### 0.18.0 - April 2025
---
- Adds ability to create new companies from the app
- Allow creation of line items against pending sales orders
- Support "extra line items" for purchase orders
- Support "extra line items" for sales orders
- Display start date for purchase orders
- Display start date for sales orders
- Fix scrolling behaviour for some widgets
- Updated search functionality
- Updated translations
### 0.17.4 - January 2025
---
- Display responsible owner for orders
- Display completion date for orders
- Updated translations
### 0.17.3 - January 2025
---
- Fixes bug which prevent dialog boxes from being dismissed correctly
- Enable editing of attachment comments
- Updated translations
### 0.17.2 - December 2024
---
- Fixed error message when printing a label to a remote machine
- Prevent notification sounds from pause media playback
- Display stock expiry information
- Updated translations
### 0.17.1 - December 2024
---
- Add support for ManufacturerPart model
- Support barcode scanning for ManufacturerPart
- Fix bugs in global search view
- Fixes barcode scanning bug which prevents scanning of DataMatrix codes
- Display "destination" information in PurchaseOrder detail view
- Pre-fill "location" field when receiving items against PurchaseOrder
- Fix display of part name in PurchaseOrderLineItem list
- Adds "assigned to me" filter for Purchase Order list
- Adds "assigned to me" filter for Sales Order list
- Updated translations
### 0.17.0 - December 2024
---
- Improved barcode scanning with new scanning library
- Prevent screen turning off when scanning barcodes
- Improved support for Stock Item test results
- Enhanced home-screen display using grid-view
- Improvements for image uploading
- Provide "upload image" shortcut on Purchase Order detail view
- Provide "upload image" shortcut on Sales Order detail view
- Clearly indicate if a StockItem is unavailable
- Improved list filtering management
- Updated translations
### 0.16.5 - September 2024
---
- Allow blank values to be entered into numerical fields
- Updated translations
### 0.16.4 - September 2024
---
- Fixes bug related to printing stock item labels
### 0.16.3 - August 2024
---
- Fixes bug relating to viewing attachment files
- Fixes bug relating to uploading attachment files
### 0.16.2 - August 2024
---
- Support "ON_HOLD" status for Purchase Orders
- Support "ON_HOLD" status for Sales Orders
- Change base icon package from FontAwesome to TablerIcons
- Bug fixes for barcode scanning
- Translation updates
### 0.16.1 - July 2024
---
- Update base packages for Android
### 0.16.0 - June 2024
---
- Add support for new file attachments API
- Drop support for legacy servers with API version < 100
### 0.15.0 - June 2024
---
- Support modern label printing API
- Improved display of stock item serial numbers
- Updated translations
### 0.14.3 - April 2024
---
- Support "active" field for Company model
- Support "active" field for SupplierPart model
- Adjustments to barcode scanning workflow
- Updated translations
### 0.14.2 - February 2024
---
- Updated error reporting
- Support for updated server API endpoints
- Updated translations
### 0.14.1 - January 2024
---
- Squashing bugs
### 0.14.0 - December 2023
---
- Adds support for Sales Orders
- Adds option to pause and resume barcode scanning with camera
- Adds option for "single shot" barcode scanning with camera
- Fixes bug when removing entire quantity of a stock item
- Add line items to purchase orders directly from the app
- Add line items to purchase order using barcode scanner
- Add line items to sales orders directly from the app
- Add line items to sales order using barcode scanner
- Allocate stock items against existing sales orders
### 0.13.0 - October 2023
---
- Adds "wedge scanner" mode, allowing use with external barcode readers
- Add ability to scan in received items using supplier barcodes
- Store API token, rather than username:password
- Ensure that user will lose access if token is revoked by server
- Improve scroll-to-refresh behaviour across multiple widgets
### 0.12.8 - September 2023
---
- Added extra options for transferring stock items
- Fixes bug where API data was not fetched with correct locale
### 0.12.7 - August 2023
---
- Bug fix for Supplier Part editing page
- Bug fix for label printing (blank template names)
- Updated translations
### 0.12.6 - July 2023
---
- Enable label printing for stock locations
- Enable label printing for parts
- Updated translation support
- Bug fixes
### 0.12.5 - July 2023
---
- Adds extra filtering options for stock items
- Updated translations
### 0.12.4 - July 2023
---
- Pre-fill stock location when transferring stock amount
- UX improvements for searching data
- Updated translations
### - 0.12.3 - June 2023
---
- Edit part parameters from within the app
- Increase visibility of stock quantity in widgets
- Improved filters for stock list
- Bug fix for editing stock item purchase price
### 0.12.2 - June 2023
---
- Adds options for configuring screen orientation
- Improvements to barcode scanning
- Translation updates
- Bug fix for scrolling long lists
### 0.12.1 - May 2023
---
- Fixes bug in purchase order form
### 0.12.0 - April 2023
---
- Add support for Project Codes
- Improve purchase order support
- Fix action button colors
- Improvements for stock item test result display
- Added Norwegian translations
- Fix serial number field when creating stock item
### 0.11.5 - April 2023
---
- Fix background image transparency for dark mode
- Fix link to Bill of Materials from Part screen
- Improvements to supplier part detail screen
- Add "notes" field to more models
### 0.11.4 - April 2023
---
- Bug fix for stock history widget
- Improved display of stock history widget
- Theme improvements for dark mode
### 0.11.3 - April 2023
---
- Fixes text color in dark mode
### 0.11.2 - April 2023
---
- Adds "dark mode" display option
- Add action to issue a purchase order
- Add action to cancel a purchase order
- Reimplement periodic checks for notifications
### 0.11.1 - April 2023
---
- Fixes keyboard bug in search widget
- Adds ability to create new purchase orders directly from the app
- Adds support for the "contact" field to purchase orders
- Improved rendering of status codes for stock items
- Added rendering of status codes for purchase orders
### 0.11.0 - April 2023
---
- Major UI updates - [see the documentation](https://docs.inventree.org/en/latest/app/app/)
- Adds globally accessible action button for "search"
- Adds globally accessible action button for "barcode scan"
- Implement context actions using floating actions buttons
- Support barcode scanning for purchase orders
### 0.10.2 - March 2023
---
- Adds support for proper currency rendering
- Fix icon for supplier part detail widget
- Support global search API endpoint
- Updated translations
### 0.10.1 - February 2023
---
- Add support for attachments on Companies
- Fix duplicate scanning of barcodes
- Updated translations
### 0.10.0 - February 2023
---
- Add support for Supplier Parts
- Updated translations
### 0.9.3 - February 2023
---
- Updates to match latest server API
- Bug fix for empty HttpResponse from server
### 0.9.2 - December 2022
---
- Support custom icons for part category
- Support custom icons for stock location
- Adjustments to notification messages
- Assorted bug fixes
- Updated translations
### 0.9.1 - December 2022
---
- Bug fixes for custom barcode actions
- Updated translations
### 0.9.0 - December 2022
---
- Added support for custom barcodes for Parts
- Added support for custom barcode for Stock Locations
- Support Part parameters
- Add support for structural part categories
- Add support for structural stock locations
- Allow deletion of attachments via app
- Adds option for controlling BOM display
- Updated translations
### 0.8.3 - September 2022
---
- Display list of assemblies which components are used in
- Fixes search input bug
### 0.8.2 - August 2022
---
- Allow serial numbers to be specified when creating new stock items
- Allow serial numbers to be edited for existing stock items
- Allow app locale to be changed manually
- Improved handling of certain errors
### 0.8.1 - August 2022
---
- Added extra filtering options for PartCategory list
- Added extra filtering options for StockLocation list
- Fixed bug related to null widget context
- Improved error handling and reporting
### 0.8.0 - July 2022
---
- Display part variants in the part detail view
- Display Bill of Materials in the part detail view
- Indicate available quantity in stock detail view
- Adds configurable filtering to various list views
- Allow stock location to be "scanned" into another location using barcode
- Improves server connection status indicator on home screen
- Display loading indicator during long-running operations
- Improved error handling and reporting
### 0.7.3 - June 2022
---
- Adds ability to display link URLs in attachments view
- Updated translations
### 0.7.2 - June 2022
---
- Add "quarantined" status flag for stock items
- Extends attachment support to stock items
- Extends attachment support to purchase orders
### 0.7.1 - May 2022
---
- Fixes issue which prevented text input in search window
- Remove support for legacy stock adjustment API
- App now requires server API version 20 (or newer)
- Updated translation files
### 0.7.0 - May 2022
---
- Refactor home screen display
- Display notification messages from InvenTree server
- Fixes duplicated display of units when showing stock quantity
- Adds ability to locate / identify stock items or locations (requires server plugin)
- Improve rendering of home screen when server is not connected
- Adds ability to load global and user settings from the server
- Translation updates
### 0.6.2 - April 2022
---
- Fixes issues related to locale support (for specific locales)
### 0.6.1 - April 2022 ### 0.6.1 - April 2022
--- ---

3
crowdin.yml Normal file
View file

@ -0,0 +1,3 @@
files:
- source: /lib/l10n/app_en.arb
translation: /lib/l10n/%locale_with_underscore%/app_%locale_with_underscore%.arb

BIN
docs/android_studio_fvm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

50
find_dart_files.py Normal file
View file

@ -0,0 +1,50 @@
"""
This script recursively finds any '.dart' files in the ./lib directory,
and generates a 'test' file which includes all these files.
This is to ensure that *all* .dart files are included in test coverage.
By default, source files which are not touched by the unit tests are not included!
Ref: https://github.com/flutter/flutter/issues/27997
"""
from pathlib import Path
if __name__ == "__main__":
dart_files = Path("lib").rglob("*.dart")
print("Discovering dart files...");
with open("test/coverage_helper_test.dart", "w") as f:
f.write("// ignore_for_file: unused_import\n\n")
f.write("// dart format off\n\n")
skips = [
"generated",
"l10n",
"dsn.dart",
]
for path in dart_files:
path = str(path)
if any([s in path for s in skips]):
continue
# Remove leading 'lib\' text
path = path[4:]
path = path.replace("\\", "/")
f.write(f'import "package:inventree/{path}";\n')
f.write("\n\n")
f.write(
"// DO NOT EDIT THIS FILE - it has been auto-generated by 'find_dart_files.py'\n"
)
f.write(
"// It has been created to ensure that *all* source file are included in coverage data\n"
)
f.write('import "package:test/test.dart";\n\n')
f.write("// Do not actually test anything!\n")
f.write("void main() {}\n")

34
ios/.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View file

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>9.0</string> <string>12.0</string>
</dict> </dict>
</plist> </plist>

View file

@ -0,0 +1,32 @@
#
# Generated file, do not edit.
#
import lldb
def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
base = frame.register["x0"].GetValueAsAddress()
page_len = frame.register["x1"].GetValueAsUnsigned()
# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
# first page to see if handled it correctly. This makes diagnosing
# misconfiguration (e.g. missing breakpoint) easier.
data = bytearray(page_len)
data[0:8] = b'IHELPED!'
error = lldb.SBError()
frame.GetThread().GetProcess().WriteMemory(base, data, error)
if not error.Success():
print(f'Failed to write into {base}[+{page_len}]', error)
return
def __lldb_init_module(debugger: lldb.SBDebugger, _):
target = debugger.GetDummyTarget()
# Caveat: must use BreakpointCreateByRegEx here and not
# BreakpointCreateByName. For some reasons callback function does not
# get carried over from dummy target for the later.
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
bp.SetAutoContinue(True)
print("-- LLDB integration loaded --")

View file

@ -0,0 +1,5 @@
#
# Generated file, do not edit.
#
command script import --relative-to-command-file flutter_lldb_helper.py

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your projects # Uncomment this line to define a global platform for your projects
platform :ios, '9.0' platform :ios, '15.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -39,7 +39,7 @@ post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end end
end end
end end

View file

@ -3,13 +3,13 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@ -57,6 +57,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
D95D9CD46BE28F7F69DBC0F6 /* Pods_Runner.framework in Frameworks */, D95D9CD46BE28F7F69DBC0F6 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -93,6 +94,7 @@
3B8B22940C363C2F0DDB698A /* Frameworks */, 3B8B22940C363C2F0DDB698A /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
tabWidth = 5;
}; };
97C146EF1CF9000F007C117D /* Products */ = { 97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
@ -157,6 +159,9 @@
dependencies = ( dependencies = (
); );
name = Runner; name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner; productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -167,26 +172,28 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0910; BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "The Chromium Authors"; ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = A5RYN267BH;
ProvisioningStyle = Automatic; ProvisioningStyle = Automatic;
}; };
}; };
}; };
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2"; compatibilityVersion = "Xcode 3.2";
developmentRegion = English; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
English,
en, en,
Base, Base,
); );
mainGroup = 97C146E51CF9000F007C117D; mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */; productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -203,7 +210,6 @@
files = ( files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
); );
@ -236,10 +242,12 @@
}; };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
); );
name = "Thin Binary"; name = "Thin Binary";
outputPaths = ( outputPaths = (
@ -250,6 +258,7 @@
}; };
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -271,47 +280,45 @@
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework", "${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework", "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/audioplayers/audioplayers.framework", "${BUILT_PRODUCTS_DIR}/audioplayers_darwin/audioplayers_darwin.framework",
"${BUILT_PRODUCTS_DIR}/camera/camera.framework", "${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework",
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework", "${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", "${BUILT_PRODUCTS_DIR}/image_picker_ios/image_picker_ios.framework",
"${BUILT_PRODUCTS_DIR}/open_file/open_file.framework", "${BUILT_PRODUCTS_DIR}/mobile_scanner/mobile_scanner.framework",
"${BUILT_PRODUCTS_DIR}/open_filex/open_filex.framework",
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
"${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework",
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework", "${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", "${BUILT_PRODUCTS_DIR}/sqflite_darwin/sqflite_darwin.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
"${BUILT_PRODUCTS_DIR}/wakelock_plus/wakelock_plus.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers_darwin.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_file.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/mobile_scanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_filex.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite_darwin.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock_plus.framework",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@ -357,6 +364,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -366,14 +374,17 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -384,6 +395,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -392,7 +404,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; INFOPLIST_KEY_CFBundleDisplayName = InvenTree;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -404,6 +417,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@ -415,8 +429,12 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; INFOPLIST_KEY_CFBundleDisplayName = InvenTree;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -432,6 +450,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -441,14 +460,17 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -459,6 +481,7 @@
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@ -473,7 +496,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; INFOPLIST_KEY_CFBundleDisplayName = InvenTree;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -485,6 +509,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
@ -494,14 +519,17 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
@ -512,6 +540,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@ -520,7 +549,8 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; INFOPLIST_KEY_CFBundleDisplayName = InvenTree;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
@ -532,6 +562,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@ -543,8 +574,12 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; INFOPLIST_KEY_CFBundleDisplayName = InvenTree;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -560,6 +595,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@ -571,8 +607,12 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; INFOPLIST_KEY_CFBundleDisplayName = InvenTree;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 15.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -608,6 +648,20 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 97C146E61CF9000F007C117D /* Project object */; rootObject = 97C146E61CF9000F007C117D /* Project object */;
} }

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View file

@ -1,10 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0910" LastUpgradeVersion = "1510"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
@ -26,6 +44,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
@ -43,11 +62,13 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View file

@ -12,23 +12,35 @@
<string>6.0</string> <string>6.0</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>
<array> <array>
<string>de</string> <string>cs-CZ</string>
<string>el</string> <string>da-DK</string>
<string>de-DE</string>
<string>el-GR</string>
<string>en</string> <string>en</string>
<string>es</string> <string>es-ES</string>
<string>fr</string> <string>es-MX</string>
<string>he</string> <string>fa-IR</string>
<string>it</string> <string>fi-FI</string>
<string>ja</string> <string>fr-FR</string>
<string>ko</string> <string>he-IL</string>
<string>nl</string> <string>hu-HU</string>
<string>no</string> <string>id-ID</string>
<string>pl</string> <string>it-IT</string>
<string>ru</string> <string>ja-JP</string>
<string>sv</string> <string>ko-KR</string>
<string>tr</string> <string>nl-NL</string>
<string>vi</string> <string>no-NO</string>
<string>pl-PL</string>
<string>pt-BR</string>
<string>pt-PT</string>
<string>ru-RU</string>
<string>sl_SI</string>
<string>sv-SE</string>
<string>th-TH</string>
<string>tr-TR</string>
<string>vi-VN</string>
<string>zh-CN</string> <string>zh-CN</string>
<string>zh-TW</string>
</array> </array>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>InvenTree</string> <string>InvenTree</string>
@ -67,5 +79,16 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>http</string>
<string>https</string>
<string>mailto</string>
<string>tel</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>

View file

@ -1,4 +1,4 @@
arb-dir: lib/l10n arb-dir: lib/l10n/collected
template-arb-file: app_en.arb template-arb-file: app_en.arb
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
output-class: I18N output-class: I18N

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,36 @@
import "package:adaptive_theme/adaptive_theme.dart";
import "package:flutter/material.dart";
import "package:inventree/helpers.dart";
import "package:one_context/one_context.dart";
bool isDarkMode() {
if (!hasContext()) {
return false;
}
import "dart:ui"; BuildContext? context = OneContext().context;
const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 1); if (context == null) {
const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1); return false;
}
const Color COLOR_CLICK = Color.fromRGBO(150, 120, 100, 0.9); return AdaptiveTheme.of(context).brightness == Brightness.dark;
}
const Color COLOR_BLUE = Color.fromRGBO(0, 0, 250, 1); // Return an "action" color based on the current theme
Color get COLOR_ACTION {
if (isDarkMode()) {
return Colors.lightBlueAccent;
} else {
return Colors.blue;
}
}
const Color COLOR_STAR = Color.fromRGBO(250, 250, 100, 1); // Set to null to use the system default
Color? COLOR_APP_BAR;
const Color COLOR_WARNING = Color.fromRGBO(250, 150, 50, 1); const Color COLOR_WARNING = Color.fromRGBO(250, 150, 50, 1);
const Color COLOR_DANGER = Color.fromRGBO(250, 50, 50, 1); const Color COLOR_DANGER = Color.fromRGBO(200, 50, 75, 1);
const Color COLOR_SUCCESS = Color.fromRGBO(50, 250, 50, 1); const Color COLOR_SUCCESS = Color.fromRGBO(100, 200, 75, 1);
const Color COLOR_PROGRESS = Color.fromRGBO(50, 50, 250, 1); const Color COLOR_PROGRESS = Color.fromRGBO(50, 100, 200, 1);
const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1);
const Color COLOR_SELECTED = Color.fromRGBO(0, 0, 0, 0.05);

View file

@ -1,68 +0,0 @@
/*
* Class for managing app-level configuration options
*/
import "package:sembast/sembast.dart";
import "package:inventree/preferences.dart";
// Settings key values
const String INV_HOME_SHOW_SUBSCRIBED = "homeShowSubscribed";
const String INV_HOME_SHOW_PO = "homeShowPo";
const String INV_HOME_SHOW_MANUFACTURERS = "homeShowManufacturers";
const String INV_HOME_SHOW_CUSTOMERS = "homeShowCustomers";
const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers";
const String INV_SOUNDS_BARCODE = "barcodeSounds";
const String INV_SOUNDS_SERVER = "serverSounds";
const String INV_PART_SUBCATEGORY = "partSubcategory";
const String INV_STOCK_SUBLOCATION = "stockSublocation";
const String INV_STOCK_SHOW_HISTORY = "stockShowHistory";
const String INV_REPORT_ERRORS = "reportErrors";
const String INV_STRICT_HTTPS = "strictHttps";
class InvenTreeSettingsManager {
factory InvenTreeSettingsManager() {
return _manager;
}
InvenTreeSettingsManager._internal();
final store = StoreRef("settings");
Future<Database> get _db async => InvenTreePreferencesDB.instance.database;
Future<dynamic> getValue(String key, dynamic backup) async {
final value = await store.record(key).get(await _db);
if (value == null) {
return backup;
}
return value;
}
// Load a boolean setting
Future<bool> getBool(String key, bool backup) async {
final dynamic value = await getValue(key, backup);
if (value is bool) {
return value;
} else {
return backup;
}
}
Future<void> setValue(String key, dynamic value) async {
await store.record(key).put(await _db, value);
}
// Ensure we only ever create a single instance of this class
static final InvenTreeSettingsManager _manager = InvenTreeSettingsManager._internal();
}

View file

@ -1,579 +0,0 @@
import "dart:io";
import "package:inventree/inventree/sentry.dart";
import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/snacks.dart";
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:one_context/one_context.dart";
import "package:qr_code_scanner/qr_code_scanner.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/l10.dart";
import "package:inventree/helpers.dart";
import "package:inventree/api.dart";
import "package:inventree/widget/location_display.dart";
import "package:inventree/widget/part_detail.dart";
import "package:inventree/widget/stock_detail.dart";
class BarcodeHandler {
/*
* Class which "handles" a barcode, by communicating with the InvenTree server,
* and handling match / unknown / error cases.
*
* Override functionality of this class to perform custom actions,
* based on the response returned from the InvenTree server
*/
BarcodeHandler();
String getOverlayText(BuildContext context) => "Barcode Overlay";
QRViewController? _controller;
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
// Called when the server "matches" a barcode
// Override this function
}
Future<void> onBarcodeUnknown(BuildContext context, Map<String, dynamic> data) async {
// Called when the server does not know about a barcode
// Override this function
failureTone();
showSnackIcon(
L10().barcodeNoMatch,
success: false,
icon: Icons.qr_code,
);
}
Future<void> onBarcodeUnhandled(BuildContext context, Map<String, dynamic> data) async {
failureTone();
// Called when the server returns an unhandled response
showServerError(L10().responseUnknown, data.toString());
_controller?.resumeCamera();
}
Future<void> processBarcode(BuildContext context, QRViewController? _controller, String barcode, {String url = "barcode/"}) async {
this._controller = _controller;
print("Scanned barcode data: ${barcode}");
if (barcode.isEmpty) {
return;
}
var response = await InvenTreeAPI().post(
url,
body: {
"barcode": barcode,
},
expectedStatusCode: 200
);
_controller?.resumeCamera();
Map<String, dynamic> data = response.asMap();
// Handle strange response from the server
if (!response.isValid() || !response.isMap()) {
onBarcodeUnknown(context, {});
// We want to know about this one!
await sentryReportMessage(
"BarcodeHandler.processBarcode returned strange value",
context: {
"data": response.data?.toString() ?? "null",
"barcode": barcode,
"url": url,
"statusCode": response.statusCode.toString(),
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
}
);
} else if (data.containsKey("error")) {
onBarcodeUnknown(context, data);
} else if (data.containsKey("success")) {
onBarcodeMatched(context, data);
} else {
onBarcodeUnhandled(context, data);
}
}
}
class BarcodeScanHandler extends BarcodeHandler {
/*
* Class for general barcode scanning.
* Scan *any* barcode without context, and then redirect app to correct view
*/
@override
String getOverlayText(BuildContext context) => L10().barcodeScanGeneral;
@override
Future<void> onBarcodeUnknown(BuildContext context, Map<String, dynamic> data) async {
failureTone();
showSnackIcon(
L10().barcodeNoMatch,
icon: FontAwesomeIcons.exclamationCircle,
success: false,
);
}
@override
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
int pk = -1;
// A stocklocation has been passed?
if (data.containsKey("stocklocation")) {
pk = (data["stocklocation"]?["pk"] ?? -1) as int;
if (pk > 0) {
successTone();
InvenTreeStockLocation().get(pk).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.of(context).pop();
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
}
});
} else {
failureTone();
showSnackIcon(
L10().invalidStockLocation,
success: false
);
}
} else if (data.containsKey("stockitem")) {
pk = (data["stockitem"]?["pk"] ?? -1) as int;
if (pk > 0) {
successTone();
InvenTreeStockItem().get(pk).then((var item) {
// Dispose of the barcode scanner
Navigator.of(context).pop();
if (item is InvenTreeStockItem) {
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
}
});
} else {
failureTone();
showSnackIcon(
L10().invalidStockItem,
success: false
);
}
} else if (data.containsKey("part")) {
pk = (data["part"]?["pk"] ?? -1) as int;
if (pk > 0) {
successTone();
InvenTreePart().get(pk).then((var part) {
// Dismiss the barcode scanner
Navigator.of(context).pop();
if (part is InvenTreePart) {
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
}
});
} else {
failureTone();
showSnackIcon(
L10().invalidPart,
success: false
);
}
} else {
failureTone();
showSnackIcon(
L10().barcodeUnknown,
success: false,
onAction: () {
OneContext().showDialog(
builder: (BuildContext context) => SimpleDialog(
title: Text(L10().unknownResponse),
children: <Widget>[
ListTile(
title: Text(L10().responseData),
subtitle: Text(data.toString()),
)
],
)
);
}
);
}
}
}
class StockItemScanIntoLocationHandler extends BarcodeHandler {
/*
* Barcode handler for scanning a provided StockItem into a scanned StockLocation
*/
StockItemScanIntoLocationHandler(this.item);
final InvenTreeStockItem item;
@override
String getOverlayText(BuildContext context) => L10().barcodeScanLocation;
@override
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
// If the barcode points to a "stocklocation", great!
if (data.containsKey("stocklocation")) {
// Extract location information
int location = (data["stocklocation"]["pk"] ?? -1) as int;
if (location == -1) {
showSnackIcon(
L10().invalidStockLocation,
success: false,
);
return;
}
// Transfer stock to specified location
final result = await item.transferStock(context, location);
if (result) {
successTone();
Navigator.of(context).pop();
showSnackIcon(
L10().barcodeScanIntoLocationSuccess,
success: true,
);
} else {
failureTone();
showSnackIcon(
L10().barcodeScanIntoLocationFailure,
success: false
);
}
} else {
failureTone();
showSnackIcon(
L10().invalidStockLocation,
success: false,
);
}
}
}
class StockLocationScanInItemsHandler extends BarcodeHandler {
/*
* Barcode handler for scanning stock item(s) into the specified StockLocation
*/
StockLocationScanInItemsHandler(this.location);
final InvenTreeStockLocation location;
@override
String getOverlayText(BuildContext context) => L10().barcodeScanItem;
@override
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
// Returned barcode must match a stock item
if (data.containsKey("stockitem")) {
int item_id = data["stockitem"]["pk"] as int;
final InvenTreeStockItem? item = await InvenTreeStockItem().get(item_id) as InvenTreeStockItem?;
if (item == null) {
failureTone();
showSnackIcon(
L10().invalidStockItem,
success: false,
);
} else if (item.locationId == location.pk) {
failureTone();
showSnackIcon(
L10().itemInLocation,
success: true
);
} else {
final result = await item.transferStock(context, location.pk);
if (result) {
successTone();
showSnackIcon(
L10().barcodeScanIntoLocationSuccess,
success: true
);
} else {
failureTone();
showSnackIcon(
L10().barcodeScanIntoLocationFailure,
success: false
);
}
}
} else {
failureTone();
// Does not match a valid stock item!
showSnackIcon(
L10().invalidStockItem,
success: false,
);
}
}
}
class UniqueBarcodeHandler extends BarcodeHandler {
/*
* Barcode handler for finding a "unique" barcode (one that does not match an item in the database)
*/
UniqueBarcodeHandler(this.callback, {this.overlayText = ""});
// Callback function when a "unique" barcode hash is found
final Function(String) callback;
final String overlayText;
@override
String getOverlayText(BuildContext context) {
if (overlayText.isEmpty) {
return L10().barcodeScanAssign;
} else {
return overlayText;
}
}
@override
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
failureTone();
// If the barcode is known, we can"t assign it to the stock item!
showSnackIcon(
L10().barcodeInUse,
icon: Icons.qr_code,
success: false
);
}
@override
Future<void> onBarcodeUnknown(BuildContext context, Map<String, dynamic> data) async {
// If the barcode is unknown, we *can* assign it to the stock item!
if (!data.containsKey("hash")) {
showServerError(
L10().missingData,
L10().barcodeMissingHash,
);
} else {
String hash = (data["hash"] ?? "") as String;
if (hash.isEmpty) {
failureTone();
showSnackIcon(
L10().barcodeError,
success: false,
);
} else {
successTone();
// Close the barcode scanner
Navigator.of(context).pop();
callback(hash);
}
}
}
}
class InvenTreeQRView extends StatefulWidget {
const InvenTreeQRView(this._handler, {Key? key}) : super(key: key);
final BarcodeHandler _handler;
@override
State<StatefulWidget> createState() => _QRViewState(_handler);
}
class _QRViewState extends State<InvenTreeQRView> {
_QRViewState(this._handler) : super();
final GlobalKey qrKey = GlobalKey(debugLabel: "QR");
QRViewController? _controller;
final BarcodeHandler _handler;
bool flash_status = false;
Future<void> updateFlashStatus() async {
final bool? status = await _controller?.getFlashStatus();
flash_status = status != null && status;
// Reload
setState(() {
});
}
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
@override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
_controller!.pauseCamera();
}
_controller!.resumeCamera();
}
void _onViewCreated(BuildContext context, QRViewController controller) {
_controller = controller;
controller.scannedDataStream.listen((barcode) {
_controller?.pauseCamera();
if (barcode.code != null) {
_handler.processBarcode(context, _controller, barcode.code ?? "");
}
});
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(L10().scanBarcode),
actions: [
IconButton(
icon: Icon(Icons.flip_camera_android),
onPressed: () {
_controller?.flipCamera();
}
),
IconButton(
icon: flash_status ? Icon(Icons.flash_off) : Icon(Icons.flash_on),
onPressed: () {
_controller?.toggleFlash();
updateFlashStatus();
},
)
],
),
body: Stack(
children: <Widget>[
Column(
children: [
Expanded(
child: QRView(
key: qrKey,
onQRViewCreated: (QRViewController controller) {
_onViewCreated(context, controller);
},
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: 300,
),
)
)
]
),
Center(
child: Column(
children: [
Spacer(),
Padding(
child: Text(_handler.getOverlayText(context),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white),
),
padding: EdgeInsets.all(20),
),
]
)
)
],
)
);
}
}
Future<void> scanQrCode(BuildContext context) async {
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeQRView(BarcodeScanHandler())));
return;
}

439
lib/barcode/barcode.dart Normal file
View file

@ -0,0 +1,439 @@
import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/helpers.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:inventree/inventree/sentry.dart";
import "package:inventree/preferences.dart";
import "package:inventree/widget/company/manufacturer_part_detail.dart";
import "package:inventree/widget/order/sales_order_detail.dart";
import "package:one_context/one_context.dart";
import "package:inventree/api.dart";
import "package:inventree/l10.dart";
import "package:inventree/barcode/camera_controller.dart";
import "package:inventree/barcode/wedge_controller.dart";
import "package:inventree/barcode/controller.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/barcode/tones.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/stock/location_display.dart";
import "package:inventree/widget/part/part_detail.dart";
import "package:inventree/widget/order/purchase_order_detail.dart";
import "package:inventree/widget/refreshable_state.dart";
import "package:inventree/widget/snacks.dart";
import "package:inventree/widget/stock/stock_detail.dart";
import "package:inventree/widget/company/company_detail.dart";
import "package:inventree/widget/company/supplier_part_detail.dart";
// Signal a barcode scan success to the user
Future<void> barcodeSuccess(String msg) async {
barcodeSuccessTone();
showSnackIcon(msg, success: true);
}
// Signal a barcode scan failure to the user
Future<void> barcodeFailure(String msg, dynamic extra) async {
barcodeFailureTone();
showSnackIcon(
msg,
success: false,
onAction: () {
if (hasContext()) {
OneContext().showDialog(
builder: (BuildContext context) => SimpleDialog(
title: Text(L10().barcodeError),
children: <Widget>[
ListTile(
title: Text(L10().responseData),
subtitle: Text(extra.toString()),
),
],
),
);
}
},
);
}
/*
* Launch a barcode scanner with a particular context and handler.
*
* - Can be called with a custom BarcodeHandler instance, or use the default handler
* - Returns a Future which resolves when the scanner is dismissed
* - The provided BarcodeHandler instance is used to handle the scanned barcode
*/
Future<Object?> scanBarcode(
BuildContext context, {
BarcodeHandler? handler,
}) async {
// Default to generic scan handler
handler ??= BarcodeScanHandler();
InvenTreeBarcodeController controller = CameraBarcodeController(handler);
// Select barcode controller based on user preference
final int barcodeControllerType =
await InvenTreeSettingsManager().getValue(
INV_BARCODE_SCAN_TYPE,
BARCODE_CONTROLLER_CAMERA,
)
as int;
switch (barcodeControllerType) {
case BARCODE_CONTROLLER_WEDGE:
controller = WedgeBarcodeController(handler);
case BARCODE_CONTROLLER_CAMERA:
default:
// Already set as default option
break;
}
return Navigator.of(context).push(
PageRouteBuilder(pageBuilder: (context, _, _) => controller, opaque: false),
);
}
/*
* Class for general barcode scanning.
* Scan *any* barcode without context, and then redirect app to correct view.
*
* Handles scanning of:
*
* - StockLocation
* - StockItem
* - Part
* - SupplierPart
* - PurchaseOrder
*/
class BarcodeScanHandler extends BarcodeHandler {
@override
String getOverlayText(BuildContext context) => L10().barcodeScanGeneral;
@override
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
barcodeFailureTone();
showSnackIcon(
L10().barcodeNoMatch,
icon: TablerIcons.exclamation_circle,
success: false,
);
}
/*
* Response when a "Part" instance is scanned
*/
Future<void> handlePart(int pk) async {
var part = await InvenTreePart().get(pk);
if (part is InvenTreePart) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(builder: (context) => PartDetailWidget(part)),
);
}
}
/*
* Response when a "StockItem" instance is scanned
*/
Future<void> handleStockItem(int pk) async {
var item = await InvenTreeStockItem().get(pk);
if (item is InvenTreeStockItem) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(builder: (context) => StockDetailWidget(item)),
);
}
}
/*
* Response when a "StockLocation" instance is scanned
*/
Future<void> handleStockLocation(int pk) async {
var loc = await InvenTreeStockLocation().get(pk);
if (loc is InvenTreeStockLocation) {
OneContext().pop();
OneContext().navigator.push(
MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)),
);
}
}
/*
* Response when a "SupplierPart" instance is scanned
*/
Future<void> handleSupplierPart(int pk) async {
var supplierPart = await InvenTreeSupplierPart().get(pk);
if (supplierPart is InvenTreeSupplierPart) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(
builder: (context) => SupplierPartDetailWidget(supplierPart),
),
);
}
}
/*
* Response when a "ManufacturerPart" instance is scanned
*/
Future<void> handleManufacturerPart(int pk) async {
var manufacturerPart = await InvenTreeManufacturerPart().get(pk);
if (manufacturerPart is InvenTreeManufacturerPart) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(
builder: (context) => ManufacturerPartDetailWidget(manufacturerPart),
),
);
}
}
Future<void> handleCompany(int pk) async {
var company = await InvenTreeCompany().get(pk);
if (company is InvenTreeCompany) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(builder: (context) => CompanyDetailWidget(company)),
);
}
}
/*
* Response when a "PurchaseOrder" instance is scanned
*/
Future<void> handlePurchaseOrder(int pk) async {
var order = await InvenTreePurchaseOrder().get(pk);
if (order is InvenTreePurchaseOrder) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(
builder: (context) => PurchaseOrderDetailWidget(order),
),
);
}
}
// Response when a SalesOrder instance is scanned
Future<void> handleSalesOrder(int pk) async {
var order = await InvenTreeSalesOrder().get(pk);
if (order is InvenTreeSalesOrder) {
OneContext().pop();
OneContext().push(
MaterialPageRoute(builder: (context) => SalesOrderDetailWidget(order)),
);
}
}
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
int pk = -1;
String model = "";
// The following model types can be matched with barcodes
List<String> validModels = [
InvenTreeStockItem.MODEL_TYPE,
InvenTreeSupplierPart.MODEL_TYPE,
InvenTreeManufacturerPart.MODEL_TYPE,
InvenTreePart.MODEL_TYPE,
InvenTreeStockLocation.MODEL_TYPE,
InvenTreeCompany.MODEL_TYPE,
];
if (InvenTreeAPI().supportsOrderBarcodes) {
validModels.add(InvenTreePurchaseOrder.MODEL_TYPE);
validModels.add(InvenTreeSalesOrder.MODEL_TYPE);
}
for (var key in validModels) {
if (data.containsKey(key)) {
try {
pk = (data[key]?["pk"] ?? -1) as int;
// Break on the first valid match found
if (pk > 0) {
model = key;
break;
}
} catch (error, stackTrace) {
sentryReportError("onBarcodeMatched", error, stackTrace);
}
}
}
// A valid result has been found
if (pk > 0 && model.isNotEmpty) {
barcodeSuccessTone();
switch (model) {
case InvenTreeStockItem.MODEL_TYPE:
await handleStockItem(pk);
return;
case InvenTreePurchaseOrder.MODEL_TYPE:
await handlePurchaseOrder(pk);
return;
case InvenTreeSalesOrder.MODEL_TYPE:
await handleSalesOrder(pk);
return;
case InvenTreeStockLocation.MODEL_TYPE:
await handleStockLocation(pk);
return;
case InvenTreeSupplierPart.MODEL_TYPE:
await handleSupplierPart(pk);
return;
case InvenTreeManufacturerPart.MODEL_TYPE:
await handleManufacturerPart(pk);
return;
case InvenTreePart.MODEL_TYPE:
await handlePart(pk);
return;
case InvenTreeCompany.MODEL_TYPE:
await handleCompany(pk);
return;
default:
// Fall through to failure state
break;
}
}
// If we get here, we have not found a valid barcode result!
barcodeFailureTone();
showSnackIcon(
L10().barcodeUnknown,
success: false,
onAction: () {
if (hasContext()) {
OneContext().showDialog(
builder: (BuildContext context) => SimpleDialog(
title: Text(L10().unknownResponse),
children: <Widget>[
ListTile(
title: Text(L10().responseData),
subtitle: Text(data.toString()),
),
],
),
);
}
},
);
}
}
/*
* Barcode handler for finding a "unique" barcode (one that does not match an item in the database)
*/
class UniqueBarcodeHandler extends BarcodeHandler {
UniqueBarcodeHandler(this.callback, {this.overlayText = ""});
// Callback function when a "unique" barcode hash is found
final Function(String) callback;
final String overlayText;
@override
String getOverlayText(BuildContext context) {
if (overlayText.isEmpty) {
return L10().barcodeScanAssign;
} else {
return overlayText;
}
}
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
if (!data.containsKey("hash") && !data.containsKey("barcode_hash")) {
showServerError("barcode/", L10().missingData, L10().barcodeMissingHash);
} else {
String barcode;
barcode = (data["barcode_data"] ?? "") as String;
if (barcode.isEmpty) {
barcodeFailureTone();
showSnackIcon(L10().barcodeError, success: false);
} else {
barcodeSuccessTone();
// Close the barcode scanner
if (OneContext.hasContext) {
OneContext().pop();
}
callback(barcode);
}
}
}
@override
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
await onBarcodeMatched(data);
}
}
SpeedDialChild customBarcodeAction(
BuildContext context,
RefreshableState state,
String barcode,
String model,
int pk,
) {
if (barcode.isEmpty) {
return SpeedDialChild(
label: L10().barcodeAssign,
child: Icon(Icons.barcode_reader),
onTap: () {
var handler = UniqueBarcodeHandler((String barcode) {
InvenTreeAPI()
.linkBarcode({model: pk.toString(), "barcode": barcode})
.then((bool result) {
showSnackIcon(
result ? L10().barcodeAssigned : L10().barcodeNotAssigned,
success: result,
);
state.refresh(context);
});
});
scanBarcode(context, handler: handler);
},
);
} else {
return SpeedDialChild(
child: Icon(Icons.barcode_reader),
label: L10().barcodeUnassign,
onTap: () {
InvenTreeAPI().unlinkBarcode({model: pk.toString()}).then((
bool result,
) {
showSnackIcon(
result ? L10().requestSuccessful : L10().requestFailed,
success: result,
);
state.refresh(context);
});
},
);
}
}

View file

@ -0,0 +1,398 @@
import "dart:math";
import "package:camera/camera.dart";
import "package:flutter/material.dart";
import "package:flutter_speed_dial/flutter_speed_dial.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/inventree/sentry.dart";
import "package:inventree/preferences.dart";
import "package:inventree/widget/snacks.dart";
import "package:mobile_scanner/mobile_scanner.dart";
import "package:one_context/one_context.dart";
import "package:wakelock_plus/wakelock_plus.dart";
import "package:inventree/l10.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/barcode/controller.dart";
/*
* Barcode controller which uses the device's camera to scan barcodes.
* Under the hood it uses the qr_code_scanner package.
*/
class CameraBarcodeController extends InvenTreeBarcodeController {
const CameraBarcodeController(BarcodeHandler handler, {Key? key})
: super(handler, key: key);
@override
State<StatefulWidget> createState() => _CameraBarcodeControllerState();
}
class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState {
_CameraBarcodeControllerState() : super();
bool flash_status = false;
int scan_delay = 500;
bool single_scanning = false;
bool scanning_paused = false;
bool multiple_barcodes = false;
String scanned_code = "";
double zoomFactor = 0.0;
final MobileScannerController controller = MobileScannerController(
autoZoom: false, // Disable autoZoom as we implement a manual slider
);
@override
void initState() {
super.initState();
_loadSettings();
WakelockPlus.enable();
}
@override
void dispose() {
super.dispose();
controller.dispose();
WakelockPlus.disable();
}
/*
* Load the barcode scanning settings
*/
Future<void> _loadSettings() async {
bool _single = await InvenTreeSettingsManager().getBool(
INV_BARCODE_SCAN_SINGLE,
false,
);
int _delay =
await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500)
as int;
if (mounted) {
setState(() {
scan_delay = _delay;
single_scanning = _single;
scanning_paused = false;
});
}
}
@override
Future<void> pauseScan() async {
if (mounted) {
setState(() {
scanning_paused = true;
});
}
}
@override
Future<void> resumeScan() async {
controller.start();
if (mounted) {
setState(() {
scanning_paused = false;
});
}
}
/*
* Callback function when a barcode is scanned
*/
Future<void> onScanSuccess(BarcodeCapture result) async {
if (!mounted || scanning_paused) {
return;
}
// TODO: Display outline of barcodes on the screen?
if (result.barcodes.isEmpty) {
setState(() {
multiple_barcodes = false;
});
} else if (result.barcodes.length > 1) {
setState(() {
multiple_barcodes = true;
});
return;
} else {
setState(() {
multiple_barcodes = false;
});
}
String barcode = result.barcodes.first.rawValue ?? "";
if (barcode.isEmpty) {
// TODO: Error message "empty barcode"
return;
}
setState(() {
scanned_code = barcode;
});
pauseScan();
await handleBarcodeData(barcode).then((_) {
if (!single_scanning && mounted) {
resumeScan();
}
});
resumeScan();
if (mounted) {
setState(() {
scanned_code = "";
multiple_barcodes = false;
});
}
}
void onControllerCreated(CameraController? controller, Exception? error) {
if (error != null) {
sentryReportError(
"CameraBarcodeController.onControllerCreated",
error,
null,
);
}
if (controller == null) {
showSnackIcon(
L10().cameraCreationError,
icon: TablerIcons.camera_x,
success: false,
);
if (OneContext.hasContext) {
Navigator.pop(OneContext().context!);
}
}
}
Widget BarcodeOverlay(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
final double width = screenSize.width;
final double height = screenSize.height;
final double D = min(width, height) * 0.8;
// Color for the barcode scan?
Color overlayColor = COLOR_ACTION;
if (multiple_barcodes) {
overlayColor = COLOR_DANGER;
} else if (scanned_code.isNotEmpty) {
overlayColor = COLOR_SUCCESS;
} else if (scanning_paused) {
overlayColor = COLOR_WARNING;
}
return Stack(
children: [
Center(
child: Container(
width: D,
height: D,
decoration: BoxDecoration(
border: Border.all(color: overlayColor, width: 4),
),
),
),
],
);
}
/*
* Build the barcode reader widget
*/
Widget BarcodeReader(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
final double width = screenSize.width;
final double height = screenSize.height;
final double D = min(width, height) * 0.8;
return MobileScanner(
controller: controller,
overlayBuilder: (context, constraints) {
return BarcodeOverlay(context);
},
scanWindow: Rect.fromCenter(
center: Offset(width / 2, height / 2),
width: D,
height: D,
),
onDetect: (result) {
onScanSuccess(result);
},
);
}
Widget topCenterOverlay() {
return SafeArea(
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(left: 10, right: 10, top: 75, bottom: 10),
child: Text(
widget.handler.getOverlayText(context),
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
Widget bottomCenterOverlay() {
String info_text = scanning_paused
? L10().barcodeScanPaused
: L10().barcodeScanPause;
String text = scanned_code.isNotEmpty ? scanned_code : info_text;
if (text.length > 50) {
text = text.substring(0, 50) + "...";
}
return SafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 75),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
Widget? buildActions(BuildContext context) {
List<SpeedDialChild> actions = [
SpeedDialChild(
child: Icon(flash_status ? TablerIcons.bulb_off : TablerIcons.bulb),
label: L10().toggleTorch,
onTap: () async {
controller.toggleTorch();
if (mounted) {
setState(() {
flash_status = !flash_status;
});
}
},
),
SpeedDialChild(
child: Icon(TablerIcons.camera),
label: L10().switchCamera,
onTap: () async {
controller.switchCamera();
},
),
];
return SpeedDial(icon: Icons.more_horiz, children: actions);
}
Widget zoomSlider() {
return Positioned(
left: 0,
right: 0,
bottom: 16,
child: Center(
child: Container(
width: 225,
height: 56,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(28),
),
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
Icon(TablerIcons.zoom_out, color: Colors.white, size: 20),
Expanded(
child: Slider(
value: zoomFactor,
min: 0.0,
max: 1.0,
activeColor: Colors.white,
inactiveColor: Colors.white.withValues(alpha: 0.3),
onChanged: (value) {
setState(() {
zoomFactor = value;
controller.setZoomScale(value);
});
},
onChangeStart: (value) async {
if (mounted) {
setState(() {
scanning_paused = true;
});
}
},
onChangeEnd: (value) async {
if (mounted) {
setState(() {
scanning_paused = false;
});
}
},
),
),
Icon(TablerIcons.zoom_in, color: Colors.white, size: 20),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: COLOR_APP_BAR,
title: Text(L10().scanBarcode),
),
floatingActionButton: buildActions(context),
body: GestureDetector(
onTap: () async {
if (mounted) {
setState(() {
// Toggle the 'scan paused' state
scanning_paused = !scanning_paused;
});
}
},
child: Stack(
children: <Widget>[
Column(children: [Expanded(child: BarcodeReader(context))]),
topCenterOverlay(),
bottomCenterOverlay(),
zoomSlider(),
],
),
),
);
}
}

103
lib/barcode/controller.dart Normal file
View file

@ -0,0 +1,103 @@
import "package:flutter/material.dart";
import "package:inventree/preferences.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/widget/progress.dart";
/*
* Generic class which provides a barcode scanner interface.
*
* When the controller is instantiated, it is passed a "handler" class,
* which is used to process the scanned barcode.
*/
class InvenTreeBarcodeController extends StatefulWidget {
const InvenTreeBarcodeController(this.handler, {Key? key}) : super(key: key);
final BarcodeHandler handler;
@override
State<StatefulWidget> createState() => InvenTreeBarcodeControllerState();
}
/*
* Base state widget for the barcode controller.
* This defines the basic interface for the barcode controller.
*/
class InvenTreeBarcodeControllerState
extends State<InvenTreeBarcodeController> {
InvenTreeBarcodeControllerState() : super();
final GlobalKey barcodeControllerKey = GlobalKey(
debugLabel: "barcodeController",
);
// Internal state flag to test if we are currently processing a barcode
bool processingBarcode = false;
/*
* Method to handle scanned data.
* Any implementing class should call this method when a barcode is scanned.
* Barcode data should be passed as a string
*/
Future<void> handleBarcodeData(String? data) async {
// Check that the data is valid, and this view is still mounted
if (!mounted || data == null || data.isEmpty) {
return;
}
// Currently processing a barcode - ignore this one
if (processingBarcode) {
return;
}
setState(() {
processingBarcode = true;
});
showLoadingOverlay();
await pauseScan();
await widget.handler.processBarcode(data);
// processBarcode may have popped the context
if (!mounted) {
hideLoadingOverlay();
return;
}
int delay =
await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500)
as int;
Future.delayed(Duration(milliseconds: delay), () {
hideLoadingOverlay();
if (mounted) {
resumeScan().then((_) {
if (mounted) {
setState(() {
processingBarcode = false;
});
}
});
}
});
}
// Hook function to "pause" the barcode scanner
Future<void> pauseScan() async {
// Implement this function in subclass
}
// Hook function to "resume" the barcode scanner
Future<void> resumeScan() async {
// Implement this function in subclass
}
/*
* Implementing classes are in control of building out the widget
*/
@override
Widget build(BuildContext context) {
return Container();
}
}

130
lib/barcode/handler.dart Normal file
View file

@ -0,0 +1,130 @@
import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/api.dart";
import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";
import "package:inventree/barcode/tones.dart";
import "package:inventree/inventree/sentry.dart";
import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/snacks.dart";
/* Generic class which "handles" a barcode, by communicating with the InvenTree server,
* and handling match / unknown / error cases.
*
* Override functionality of this class to perform custom actions,
* based on the response returned from the InvenTree server
*/
class BarcodeHandler {
BarcodeHandler();
// Return the text to display on the barcode overlay
// Note: Will be overridden by child classes
String getOverlayText(BuildContext context) => "Barcode Overlay";
// Called when the server "matches" a barcode
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// Override this function
}
// Called when the server does not know about a barcode
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
// Override this function
barcodeFailureTone();
showSnackIcon(
(data["error"] ?? L10().barcodeNoMatch) as String,
success: false,
icon: Icons.qr_code,
);
}
// Called when the server returns an unhandled response
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
barcodeFailureTone();
showServerError("barcode/", L10().responseUnknown, data.toString());
}
/*
* Base function to capture and process barcode data.
*
* Returns true only if the barcode scanner should remain open
*/
Future<void> processBarcode(
String barcode, {
String url = "barcode/",
Map<String, dynamic> extra_data = const {},
}) async {
debug("Scanned barcode data: '${barcode}'");
barcode = barcode.trim();
// Empty barcode is invalid
if (barcode.isEmpty) {
barcodeFailureTone();
showSnackIcon(
L10().barcodeError,
icon: TablerIcons.exclamation_circle,
success: false,
);
return;
}
APIResponse? response;
try {
response = await InvenTreeAPI().post(
url,
body: {"barcode": barcode, ...extra_data},
expectedStatusCode: null, // Do not show an error on "unexpected code"
);
} catch (error, stackTrace) {
sentryReportError("Barcode.processBarcode", error, stackTrace);
response = null;
}
if (response == null) {
barcodeFailureTone();
showSnackIcon(L10().barcodeError, success: false);
return;
}
debug("Barcode scan response" + response.data.toString());
Map<String, dynamic> data = response.asMap();
// Handle strange response from the server
if (!response.isValid() || !response.isMap()) {
await onBarcodeUnknown({});
showSnackIcon(L10().serverError, success: false);
// We want to know about this one!
await sentryReportMessage(
"BarcodeHandler.processBarcode returned unexpected value",
context: {
"data": response.data?.toString() ?? "null",
"barcode": barcode,
"url": url,
"statusCode": response.statusCode.toString(),
"valid": response.isValid().toString(),
"error": response.error,
"errorDetail": response.errorDetail,
"className": "${this}",
},
);
} else if (data.containsKey("success")) {
await onBarcodeMatched(data);
} else if ((response.statusCode >= 400) || data.containsKey("error")) {
await onBarcodeUnknown(data);
} else {
await onBarcodeUnhandled(data);
}
}
}

View file

@ -0,0 +1,194 @@
import "package:flutter/material.dart";
import "package:inventree/preferences.dart";
import "package:one_context/one_context.dart";
import "package:inventree/l10.dart";
import "package:inventree/barcode/barcode.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/barcode/tones.dart";
import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/snacks.dart";
/*
* Barcode handler class for scanning a supplier barcode to receive a part
*
* - The class can be initialized by optionally passing a valid, placed PurchaseOrder object
* - Expects to scan supplier barcode, possibly containing order_number and quantity
* - If location or quantity information wasn't provided, show a form to fill it in
*/
class POReceiveBarcodeHandler extends BarcodeHandler {
POReceiveBarcodeHandler({this.purchaseOrder, this.location, this.lineItem});
InvenTreePurchaseOrder? purchaseOrder;
InvenTreeStockLocation? location;
InvenTreePOLineItem? lineItem;
@override
String getOverlayText(BuildContext context) => L10().barcodeReceivePart;
@override
Future<void> processBarcode(
String barcode, {
String url = "barcode/po-receive/",
Map<String, dynamic> extra_data = const {},
}) async {
final bool confirm = await InvenTreeSettingsManager().getBool(
INV_PO_CONFIRM_SCAN,
true,
);
final po_extra_data = {
"purchase_order": purchaseOrder?.pk,
"location": location?.pk,
"line_item": lineItem?.pk,
"auto_allocate": !confirm,
...extra_data,
};
return super.processBarcode(barcode, url: url, extra_data: po_extra_data);
}
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
if (data.containsKey("lineitem") || data.containsKey("success")) {
barcodeSuccess(L10().receivedItem);
return;
} else {
return onBarcodeUnknown(data);
}
}
@override
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
if (!data.containsKey("action_required") || !data.containsKey("lineitem")) {
return super.onBarcodeUnhandled(data);
}
final lineItemData = data["lineitem"] as Map<String, dynamic>;
if (!lineItemData.containsKey("pk") ||
!lineItemData.containsKey("purchase_order")) {
barcodeFailureTone();
showSnackIcon(L10().missingData, success: false);
}
// At minimum, we need the line item ID value
final int? lineItemId = lineItemData["pk"] as int?;
if (lineItemId == null) {
barcodeFailureTone();
return;
}
InvenTreePOLineItem? lineItem =
await InvenTreePOLineItem().get(lineItemId) as InvenTreePOLineItem?;
if (lineItem == null) {
barcodeFailureTone();
return;
}
// Next, extract the "optional" fields
// Extract information from the returned server response
double? quantity = double.tryParse(
(lineItemData["quantity"] ?? "0").toString(),
);
int? destination = lineItemData["location"] as int?;
String? barcode = data["barcode_data"] as String?;
// Discard the barcode scanner at this stage
if (OneContext.hasContext) {
OneContext().pop();
}
await lineItem.receive(
OneContext().context!,
destination: destination,
quantity: quantity,
barcode: barcode,
onSuccess: () {
showSnackIcon(L10().receivedItem, success: true);
},
);
}
@override
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
barcodeFailureTone();
showSnackIcon(
data["error"] as String? ?? L10().barcodeError,
success: false,
);
}
}
/*
* Barcode handler to add a line item to a purchase order
*/
class POAllocateBarcodeHandler extends BarcodeHandler {
POAllocateBarcodeHandler({this.purchaseOrder});
InvenTreePurchaseOrder? purchaseOrder;
@override
String getOverlayText(BuildContext context) => L10().scanSupplierPart;
@override
Future<void> processBarcode(
String barcode, {
String url = "barcode/po-allocate/",
Map<String, dynamic> extra_data = const {},
}) {
final po_extra_data = {"purchase_order": purchaseOrder?.pk, ...extra_data};
return super.processBarcode(barcode, url: url, extra_data: po_extra_data);
}
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// Server must respond with a suppliertpart instance
if (!data.containsKey("supplierpart")) {
return onBarcodeUnknown(data);
}
dynamic supplier_part = data["supplierpart"];
int supplier_part_pk = -1;
if (supplier_part is Map<String, dynamic>) {
supplier_part_pk = (supplier_part["pk"] ?? -1) as int;
} else {
return onBarcodeUnknown(data);
}
// Dispose of the barcode scanner
if (OneContext.hasContext) {
OneContext().pop();
}
final context = OneContext().context!;
var fields = InvenTreePOLineItem().formFields();
fields["order"]?["value"] = purchaseOrder!.pk;
fields["part"]?["hidden"] = false;
fields["part"]?["value"] = supplier_part_pk;
InvenTreePOLineItem().createForm(
context,
L10().lineItemAdd,
fields: fields,
);
}
@override
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
print("onBarcodeUnhandled:");
print(data.toString());
super.onBarcodeUnhandled(data);
}
}

View file

@ -0,0 +1,163 @@
import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/api_form.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/sales_order.dart";
import "package:one_context/one_context.dart";
import "package:inventree/l10.dart";
import "package:inventree/barcode/barcode.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/barcode/tones.dart";
import "package:inventree/widget/snacks.dart";
/*
* Barcode handler class for scanning a new part into a SalesOrder
*/
class SOAddItemBarcodeHandler extends BarcodeHandler {
SOAddItemBarcodeHandler({this.salesOrder});
InvenTreeSalesOrder? salesOrder;
@override
String getOverlayText(BuildContext context) => L10().barcodeScanPart;
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// Extract the part ID from the returned data
int part_id = -1;
if (data.containsKey("part")) {
part_id = (data["part"] ?? {} as Map<String, dynamic>)["pk"] as int;
}
if (part_id <= 0) {
return onBarcodeUnknown(data);
}
// Request the part from the server
var part = await InvenTreePart().get(part_id);
if (part is InvenTreePart) {
if (part.isSalable) {
// Dispose of the barcode scanner
if (OneContext.hasContext) {
OneContext().pop();
}
final context = OneContext().context!;
var fields = InvenTreeSOLineItem().formFields();
fields["order"]?["value"] = salesOrder!.pk;
fields["order"]?["hidden"] = true;
fields["part"]?["value"] = part.pk;
fields["part"]?["hidden"] = false;
InvenTreeSOLineItem().createForm(
context,
L10().lineItemAdd,
fields: fields,
);
} else {
barcodeFailureTone();
showSnackIcon(L10().partNotSalable, success: false);
}
} else {
// Failed to fetch part
return onBarcodeUnknown(data);
}
}
}
class SOAllocateStockHandler extends BarcodeHandler {
SOAllocateStockHandler({this.salesOrder, this.lineItem, this.shipment});
InvenTreeSalesOrder? salesOrder;
InvenTreeSOLineItem? lineItem;
InvenTreeSalesOrderShipment? shipment;
@override
String getOverlayText(BuildContext context) => L10().allocateStock;
@override
Future<void> processBarcode(
String barcode, {
String url = "barcode/so-allocate/",
Map<String, dynamic> extra_data = const {},
}) {
final so_extra_data = {
"sales_order": salesOrder?.pk,
"shipment": shipment?.pk,
"line": lineItem?.pk,
...extra_data,
};
return super.processBarcode(barcode, url: url, extra_data: so_extra_data);
}
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
if (!data.containsKey("line_item")) {
return onBarcodeUnknown(data);
}
barcodeSuccess(L10().allocated);
}
@override
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
if (!data.containsKey("action_required") ||
!data.containsKey("line_item")) {
return super.onBarcodeUnhandled(data);
}
// Prompt user for extra information to create the allocation
var fields = InvenTreeSOLineItem().allocateFormFields();
// Update fields with data gathered from the API response
fields["line_item"]?["value"] = data["line_item"];
Map<String, dynamic> stock_filters = {"in_stock": true, "available": true};
if (data.containsKey("part")) {
stock_filters["part"] = data["part"];
}
fields["stock_item"]?["filters"] = stock_filters;
fields["stock_item"]?["value"] = data["stock_item"];
fields["quantity"]?["value"] = data["quantity"];
fields["shipment"]?["value"] = data["shipment"];
fields["shipment"]?["filters"] = {"order": salesOrder!.pk.toString()};
final context = OneContext().context!;
launchApiForm(
context,
L10().allocateStock,
salesOrder!.allocate_url,
fields,
method: "POST",
icon: TablerIcons.transition_right,
onSuccess: (data) async {
showSnackIcon(L10().allocated, success: true);
},
);
}
@override
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
barcodeFailureTone();
showSnackIcon(
data["error"] as String? ?? L10().barcodeError,
success: false,
);
}
}

270
lib/barcode/stock.dart Normal file
View file

@ -0,0 +1,270 @@
import "package:flutter/cupertino.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/api_form.dart";
import "package:inventree/preferences.dart";
import "package:one_context/one_context.dart";
import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";
import "package:inventree/barcode/barcode.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/barcode/tones.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/snacks.dart";
/*
* Generic class for scanning a StockLocation.
*
* - Validates that the scanned barcode matches a valid StockLocation
* - Runs a "callback" function if a valid StockLocation is found
*/
class BarcodeScanStockLocationHandler extends BarcodeHandler {
@override
String getOverlayText(BuildContext context) => L10().barcodeScanLocation;
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// We expect that the barcode points to a 'stocklocation'
if (data.containsKey("stocklocation")) {
int _loc = (data["stocklocation"]?["pk"] ?? -1) as int;
// A valid stock location!
if (_loc > 0) {
debug("Scanned stock location ${_loc}");
final bool result = await onLocationScanned(_loc);
if (result && hasContext()) {
OneContext().pop();
}
return;
}
}
// If we get to this point, something went wrong during the scan process
barcodeFailureTone();
showSnackIcon(L10().invalidStockLocation, success: false);
}
// Callback function which runs when a valid StockLocation is scanned
// If this function returns 'true' the barcode scanning dialog will be closed
Future<bool> onLocationScanned(int locationId) async {
// Re-implement this for particular subclass
return false;
}
}
/*
* Generic class for scanning a StockItem
*
* - Validates that the scanned barcode matches a valid StockItem
* - Runs a "callback" function if a valid StockItem is found
*/
class BarcodeScanStockItemHandler extends BarcodeHandler {
@override
String getOverlayText(BuildContext context) => L10().barcodeScanItem;
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// We expect that the barcode points to a 'stockitem'
if (data.containsKey("stockitem")) {
int _item = (data["stockitem"]?["pk"] ?? -1) as int;
// A valid stock location!
if (_item > 0) {
barcodeSuccessTone();
bool result = await onItemScanned(_item);
if (result && OneContext.hasContext) {
OneContext().pop();
return;
}
}
}
// If we get to this point, something went wrong during the scan process
barcodeFailureTone();
showSnackIcon(L10().invalidStockItem, success: false);
}
// Callback function which runs when a valid StockItem is scanned
Future<bool> onItemScanned(int itemId) async {
// Re-implement this for particular subclass
return false;
}
}
/*
* Barcode handler for scanning a provided StockItem into a scanned StockLocation.
*
* - The class is initialized by passing a valid StockItem object
* - Expects to scan barcode for a StockLocation
* - The StockItem is transferred into the scanned location
*/
class StockItemScanIntoLocationHandler extends BarcodeScanStockLocationHandler {
StockItemScanIntoLocationHandler(this.item);
final InvenTreeStockItem item;
@override
Future<bool> onLocationScanned(int locationId) async {
final bool confirm = await InvenTreeSettingsManager().getBool(
INV_STOCK_CONFIRM_SCAN,
false,
);
bool result = false;
if (confirm) {
Map<String, dynamic> fields = item.transferFields();
// Override location with scanned value
fields["location"]?["value"] = locationId;
launchApiForm(
OneContext().context!,
L10().transferStock,
InvenTreeStockItem.transferStockUrl(),
fields,
method: "POST",
icon: TablerIcons.transfer,
onSuccess: (data) async {
showSnackIcon(L10().stockItemUpdated, success: true);
},
);
return true;
} else {
result = await item.transferStock(locationId);
}
if (result) {
barcodeSuccess(L10().barcodeScanIntoLocationSuccess);
} else {
barcodeFailureTone();
showSnackIcon(L10().barcodeScanIntoLocationFailure, success: false);
}
return result;
}
}
/*
* Barcode handler for scanning stock item(s) into the specified StockLocation.
*
* - The class is initialized by passing a valid StockLocation object
* - Expects to scan a barcode for a StockItem
* - The scanned StockItem is transferred into the provided StockLocation
*/
class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler {
StockLocationScanInItemsHandler(this.location);
final InvenTreeStockLocation location;
@override
String getOverlayText(BuildContext context) => L10().barcodeScanItem;
@override
Future<bool> onItemScanned(int itemId) async {
final InvenTreeStockItem? item =
await InvenTreeStockItem().get(itemId) as InvenTreeStockItem?;
final bool confirm = await InvenTreeSettingsManager().getBool(
INV_STOCK_CONFIRM_SCAN,
false,
);
bool result = false;
if (item != null) {
// Item is already *in* the specified location
if (item.locationId == location.pk) {
barcodeFailureTone();
showSnackIcon(L10().itemInLocation, success: true);
return false;
} else {
if (confirm) {
Map<String, dynamic> fields = item.transferFields();
// Override location with provided location value
fields["location"]?["value"] = location.pk;
launchApiForm(
OneContext().context!,
L10().transferStock,
InvenTreeStockItem.transferStockUrl(),
fields,
method: "POST",
icon: TablerIcons.transfer,
onSuccess: (data) async {
showSnackIcon(L10().stockItemUpdated, success: true);
},
);
return true;
} else {
result = await item.transferStock(location.pk);
showSnackIcon(
result
? L10().barcodeScanIntoLocationSuccess
: L10().barcodeScanIntoLocationFailure,
success: result,
);
}
}
}
// We always return false here, to ensure the barcode scan dialog remains open
return false;
}
}
/*
* Barcode handler class for scanning a StockLocation into another StockLocation
*
* - The class is initialized by passing a valid StockLocation object
* - Expects to scan barcode for another *parent* StockLocation
* - The scanned StockLocation is set as the "parent" of the provided StockLocation
*/
class ScanParentLocationHandler extends BarcodeScanStockLocationHandler {
ScanParentLocationHandler(this.location);
final InvenTreeStockLocation location;
@override
Future<bool> onLocationScanned(int locationId) async {
final response = await location.update(
values: {"parent": locationId.toString()},
expectedStatusCode: null,
);
switch (response.statusCode) {
case 200:
case 201:
barcodeSuccess(L10().barcodeScanIntoLocationSuccess);
return true;
case 400: // Invalid parent location chosen
barcodeFailureTone();
showSnackIcon(L10().invalidStockLocation, success: false);
return false;
default:
barcodeFailureTone();
showSnackIcon(
L10().barcodeScanIntoLocationFailure,
success: false,
actionText: L10().details,
onAction: () {
showErrorDialog(L10().barcodeError, response: response);
},
);
return false;
}
}
}

25
lib/barcode/tones.dart Normal file
View file

@ -0,0 +1,25 @@
import "package:inventree/helpers.dart";
import "package:inventree/preferences.dart";
/*
* Play an audible 'success' alert to the user.
*/
Future<void> barcodeSuccessTone() async {
final bool en =
await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true)
as bool;
if (en) {
playAudioFile("sounds/barcode_scan.mp3");
}
}
Future<void> barcodeFailureTone() async {
final bool en =
await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true)
as bool;
if (en) {
playAudioFile("sounds/barcode_error.mp3");
}
}

View file

@ -0,0 +1,146 @@
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/barcode/controller.dart";
import "package:inventree/barcode/handler.dart";
import "package:inventree/l10.dart";
import "package:inventree/helpers.dart";
/*
* Barcode controller which acts as a keyboard wedge,
* intercepting barcode data which is entered as rapid keyboard presses
*/
class WedgeBarcodeController extends InvenTreeBarcodeController {
const WedgeBarcodeController(BarcodeHandler handler, {Key? key})
: super(handler, key: key);
@override
State<StatefulWidget> createState() => _WedgeBarcodeControllerState();
}
class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState {
_WedgeBarcodeControllerState() : super();
bool canScan = true;
bool get scanning => mounted && canScan;
final FocusNode _focusNode = FocusNode();
List<String> _scannedCharacters = [];
DateTime? _lastScanTime;
@override
Future<void> pauseScan() async {
if (mounted) {
setState(() {
canScan = false;
});
}
}
@override
Future<void> resumeScan() async {
if (mounted) {
setState(() {
canScan = true;
});
}
}
// Callback for a single key press / scan
void handleKeyEvent(KeyEvent event) {
if (!scanning) {
return;
}
// Look only for key-down events
if (event is! KeyDownEvent) {
return;
}
// Ignore events without a character code
if (event.character == null) {
return;
}
DateTime now = DateTime.now();
// Throw away old characters
if (_lastScanTime == null ||
_lastScanTime!.isBefore(now.subtract(Duration(milliseconds: 250)))) {
_scannedCharacters.clear();
}
_lastScanTime = now;
if (event.character == "\n") {
if (_scannedCharacters.isNotEmpty) {
// Debug output required for unit testing
debug("scanned: ${_scannedCharacters.join()}");
handleBarcodeData(_scannedCharacters.join());
}
_scannedCharacters.clear();
} else {
_scannedCharacters.add(event.character!);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: COLOR_APP_BAR,
title: Text(L10().scanBarcode),
),
backgroundColor: Colors.black.withValues(alpha: 0.9),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(flex: 5),
Icon(TablerIcons.barcode, size: 64),
Spacer(flex: 5),
KeyboardListener(
autofocus: true,
focusNode: _focusNode,
child: SizedBox(
child: CircularProgressIndicator(
color: scanning ? COLOR_ACTION : COLOR_PROGRESS,
),
width: 64,
height: 64,
),
onKeyEvent: (event) {
handleKeyEvent(event);
},
// onBarcodeScanned: (String barcode) {
// debug("scanned: ${barcode}");
// if (scanning) {
// // Process the barcode data
// handleBarcodeData(barcode);
// }
// },
),
Spacer(flex: 5),
Padding(
child: Text(
widget.handler.getOverlayText(context),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
padding: EdgeInsets.all(20),
),
],
),
),
);
}
}

7
lib/dsn.dart Normal file
View file

@ -0,0 +1,7 @@
/*
* For integration with sentry.io, fill out the SENTRY_DSN_KEY value below.
* This should be set to a valid DSN key, from your sentry.io account
*
*/
String SENTRY_DSN_KEY =
"https://fea705aa4b8e4c598dcf9b146b3d1b86@o378676.ingest.sentry.io/5202450";

View file

@ -1,3 +0,0 @@
// Dummy DSN to use for unit testing, etc
const String SENTRY_DSN_KEY = "https://12345678901234567890@abcdef.ingest.sentry.io/11223344";

View file

@ -1,76 +0,0 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: camel_case_types
// ignore_for_file: prefer_single_quotes
//This file is automatically generated. DO NOT EDIT, all your changes would be lost.
class S implements WidgetsLocalizations {
const S();
static const GeneratedLocalizationsDelegate delegate = GeneratedLocalizationsDelegate();
static S of(BuildContext context) => Localizations.of<S>(context, WidgetsLocalizations);
@override
TextDirection get textDirection => TextDirection.ltr;
}
class en extends S {
const en();
}
class GeneratedLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
const GeneratedLocalizationsDelegate();
List<Locale> get supportedLocales {
return const <Locale>[
const Locale("en", ""),
];
}
LocaleResolutionCallback resolution({Locale fallback}) {
return (Locale locale, Iterable<Locale> supported) {
final Locale languageLocale = new Locale(locale.languageCode, "");
if (supported.contains(locale))
return locale;
else if (supported.contains(languageLocale))
return languageLocale;
else {
final Locale fallbackLocale = fallback ?? supported.first;
return fallbackLocale;
}
};
}
@override
Future<WidgetsLocalizations> load(Locale locale) {
final String lang = getLang(locale);
switch (lang) {
case "en":
return new SynchronousFuture<WidgetsLocalizations>(const en());
default:
return new SynchronousFuture<WidgetsLocalizations>(const S());
}
}
@override
bool isSupported(Locale locale) => supportedLocales.contains(locale);
@override
bool shouldReload(GeneratedLocalizationsDelegate old) => false;
}
String getLang(Locale l) => l.countryCode != null && l.countryCode.isEmpty
? l.languageCode
: l.toString();

View file

@ -7,31 +7,173 @@
* supressing trailing zeroes * supressing trailing zeroes
*/ */
import "dart:io";
import "package:currency_formatter/currency_formatter.dart";
import "package:one_context/one_context.dart";
import "package:url_launcher/url_launcher.dart";
import "package:audioplayers/audioplayers.dart"; import "package:audioplayers/audioplayers.dart";
import "package:inventree/app_settings.dart";
import "package:inventree/l10.dart";
import "package:inventree/widget/snacks.dart";
List<String> debug_messages = [];
void clearDebugMessage() => debug_messages.clear();
int debugMessageCount() {
print("Debug Messages: ${debug_messages.length}");
return debug_messages.length;
}
// Check if the debug log contains a given message
bool debugContains(String msg, {bool raiseAssert = true}) {
bool result = false;
for (String element in debug_messages) {
if (element.contains(msg)) {
result = true;
break;
}
}
if (!result) {
print("Debug does not contain expected string: '${msg}'");
}
if (raiseAssert) {
assert(result);
}
return result;
}
bool isTesting() {
return Platform.environment.containsKey("FLUTTER_TEST");
}
bool hasContext() {
try {
return !isTesting() && OneContext.hasContext;
} catch (error) {
return false;
}
}
/*
* Display a debug message if we are in testing mode, or running in debug mode
*/
void debug(dynamic msg) {
if (Platform.environment.containsKey("FLUTTER_TEST")) {
debug_messages.add(msg.toString());
}
print("DEBUG: ${msg.toString()}");
}
/*
* Simplify string representation of a floating point value
* Basically, don't display fractional component if it is an integer
*/
String simpleNumberString(double number) { String simpleNumberString(double number) {
// Ref: https://stackoverflow.com/questions/55152175/how-to-remove-trailing-zeros-using-dart if (number.toInt() == number) {
return number.toInt().toString();
return number.toStringAsFixed(number.truncateToDouble() == number ? 0 : 1); } else {
} return number.toString();
Future<void> successTone() async {
final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool;
if (en) {
final player = AudioCache();
player.play("sounds/barcode_scan.mp3");
} }
} }
Future <void> failureTone() async { /*
* Play an audio file from the requested path.
*
* Note: If OneContext module fails the 'hasContext' check,
* we will not attempt to play the sound
*/
Future<void> playAudioFile(String path) async {
// Debug message for unit testing
debug("Playing audio file: '${path}'");
final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool; if (!hasContext()) {
return;
if (en) {
final player = AudioCache();
player.play("sounds/barcode_error.mp3");
} }
}
final player = AudioPlayer();
// Specify context options for the audio player
// Ref: https://github.com/inventree/inventree-app/issues/582
player.setAudioContext(
AudioContext(
android: AudioContextAndroid(
usageType: AndroidUsageType.notification,
audioFocus: AndroidAudioFocus.none,
),
iOS: AudioContextIOS(),
),
);
player.play(AssetSource(path));
}
// Open an external URL
Future<void> openLink(String url) async {
final link = Uri.parse(url);
try {
await launchUrl(link);
} catch (e) {
showSnackIcon(L10().error, success: false);
}
}
/*
* Helper function for rendering a money / currency object as a String
*/
String renderCurrency(double? amount, String currency, {int decimals = 2}) {
if (amount == null || amount.isInfinite || amount.isNaN) return "-";
currency = currency.trim();
if (currency.isEmpty) return "-";
CurrencyFormat fmt =
CurrencyFormat.fromCode(currency.toLowerCase()) ?? CurrencyFormat.usd;
String value = CurrencyFormatter.format(amount, fmt);
return value;
}
bool isValidNumber(double? value) {
return value != null && !value.isNaN && !value.isInfinite;
}
/*
* Render a "range" of prices between two values.
*/
String formatPriceRange(
double? minPrice,
double? maxPrice, {
String? currency,
}) {
// Account for empty or null values
if (!isValidNumber(minPrice) && !isValidNumber(maxPrice)) {
return "-";
}
if (isValidNumber(minPrice) && isValidNumber(maxPrice)) {
// Two values are equal
if (minPrice == maxPrice) {
return renderCurrency(minPrice, currency ?? "USD");
} else {
return "${renderCurrency(minPrice, currency ?? "USD")} - ${renderCurrency(maxPrice, currency ?? "USD")}";
}
}
if (isValidNumber(minPrice)) {
return renderCurrency(minPrice, currency ?? "USD");
} else if (isValidNumber(maxPrice)) {
return renderCurrency(maxPrice, currency ?? "USD");
} else {
return "-";
}
}

View file

@ -0,0 +1,176 @@
import "dart:io";
import "package:flutter/cupertino.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/api.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/sentry.dart";
import "package:inventree/l10.dart";
import "package:inventree/widget/fields.dart";
import "package:inventree/widget/snacks.dart";
import "package:path/path.dart" as path;
class InvenTreeAttachment extends InvenTreeModel {
// Class representing an "attachment" file
InvenTreeAttachment() : super();
InvenTreeAttachment.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeAttachment createFromJson(Map<String, dynamic> json) =>
InvenTreeAttachment.fromJson(json);
@override
String get URL => "attachment/";
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {"link": {}, "comment": {}};
if (!hasLink) {
fields.remove("link");
}
return fields;
}
// The model type of the instance this attachment is associated with
String get modelType => getString("model_type");
// The ID of the instance this attachment is associated with
int get modelId => getInt("model_id");
String get attachment => getString("attachment");
bool get hasAttachment => attachment.isNotEmpty;
// Return the filename of the attachment
String get filename {
return attachment.split("/").last;
}
IconData get icon {
String fn = filename.toLowerCase();
if (fn.endsWith(".pdf")) {
return TablerIcons.file_type_pdf;
} else if (fn.endsWith(".csv")) {
return TablerIcons.file_type_csv;
} else if (fn.endsWith(".doc") || fn.endsWith(".docx")) {
return TablerIcons.file_type_doc;
} else if (fn.endsWith(".xls") || fn.endsWith(".xlsx")) {
return TablerIcons.file_type_xls;
}
// Image formats
final List<String> img_formats = [".png", ".jpg", ".gif", ".bmp", ".svg"];
for (String fmt in img_formats) {
if (fn.endsWith(fmt)) {
return TablerIcons.file_type_jpg;
}
}
return TablerIcons.file;
}
String get comment => getString("comment");
DateTime? get uploadDate {
if (jsondata.containsKey("upload_date")) {
return DateTime.tryParse((jsondata["upload_date"] ?? "") as String);
} else {
return null;
}
}
// Return a count of how many attachments exist against the specified model ID
Future<int> countAttachments(String modelType, int modelId) async {
Map<String, String> filters = {};
if (!api.supportsModernAttachments) {
return 0;
}
filters["model_type"] = modelType;
filters["model_id"] = modelId.toString();
return count(filters: filters);
}
Future<bool> uploadAttachment(
File attachment,
String modelType,
int modelId, {
String comment = "",
Map<String, String> fields = const {},
}) async {
// Ensure that the correct reference field is set
Map<String, String> data = Map<String, String>.from(fields);
String url = URL;
if (comment.isNotEmpty) {
data["comment"] = comment;
}
data["model_type"] = modelType;
data["model_id"] = modelId.toString();
final APIResponse response = await InvenTreeAPI().uploadFile(
url,
attachment,
method: "POST",
name: "attachment",
fields: data,
);
return response.successful();
}
Future<bool> uploadImage(
String modelType,
int modelId, {
String prefix = "InvenTree",
}) async {
bool result = false;
await FilePickerDialog.pickImageFromCamera().then((File? file) {
if (file != null) {
String dir = path.dirname(file.path);
String ext = path.extension(file.path);
String now = DateTime.now().toIso8601String().replaceAll(":", "-");
// Rename the file with a unique name
String filename = "${dir}/${prefix}_image_${now}${ext}";
try {
return file.rename(filename).then((File renamed) {
return uploadAttachment(renamed, modelType, modelId).then((
success,
) {
result = success;
showSnackIcon(
result ? L10().imageUploadSuccess : L10().imageUploadFailure,
success: result,
);
});
});
} catch (error, stackTrace) {
sentryReportError("uploadImage", error, stackTrace);
showSnackIcon(L10().imageUploadFailure, success: false);
}
}
});
return result;
}
/*
* Download this attachment file
*/
Future<void> downloadAttachment() async {
await InvenTreeAPI().downloadFile(attachment);
}
}

63
lib/inventree/bom.dart Normal file
View file

@ -0,0 +1,63 @@
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.dart";
/*
* Class representing the BomItem database model
*/
class InvenTreeBomItem extends InvenTreeModel {
InvenTreeBomItem() : super();
InvenTreeBomItem.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeBomItem.fromJson(json);
@override
String get URL => "bom/";
@override
Map<String, String> defaultFilters() {
return {
"sub_part_detail": "true",
"part_detail": "true",
"show_pricing": "false",
};
}
// Extract the 'reference' value associated with this BomItem
String get reference => getString("reference");
// Extract the 'quantity' value associated with this BomItem
double get quantity => getDouble("quantity");
// Extract the ID of the related part
int get partId => getInt("part");
// Return a Part instance for the referenced part
InvenTreePart? get part {
if (jsondata.containsKey("part_detail")) {
dynamic data = jsondata["part_detail"] ?? {};
if (data is Map<String, dynamic>) {
return InvenTreePart.fromJson(data);
}
}
return null;
}
// Return a Part instance for the referenced sub-part
InvenTreePart? get subPart {
if (jsondata.containsKey("sub_part_detail")) {
dynamic data = jsondata["sub_part_detail"] ?? {};
if (data is Map<String, dynamic>) {
return InvenTreePart.fromJson(data);
}
}
return null;
}
// Extract the ID of the related sub-part
int get subPartId => getInt("sub_part");
}

View file

@ -1,16 +1,17 @@
import "dart:async"; import "dart:async";
import "package:flutter/material.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/inventree/purchase_order.dart";
import "package:inventree/widget/company/company_detail.dart";
import "package:inventree/widget/company/supplier_part_detail.dart";
/* /*
* The InvenTreeCompany class repreents the Company model in the InvenTree database. * The InvenTreeCompany class represents the Company model in the InvenTree database.
*/ */
class InvenTreeCompany extends InvenTreeModel { class InvenTreeCompany extends InvenTreeModel {
InvenTreeCompany() : super(); InvenTreeCompany() : super();
InvenTreeCompany.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeCompany.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@ -18,9 +19,26 @@ class InvenTreeCompany extends InvenTreeModel {
@override @override
String get URL => "company/"; String get URL => "company/";
static const String MODEL_TYPE = "company";
@override @override
Map<String, dynamic> formFields() { Future<Object?> goToDetailPage(BuildContext context) async {
return { return Navigator.push(
context,
MaterialPageRoute(builder: (context) => CompanyDetailWidget(this)),
);
}
@override
List<String> get rolesRequired => [
"purchase_order",
"sales_order",
"return_order",
];
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"name": {}, "name": {},
"description": {}, "description": {},
"website": {}, "website": {},
@ -29,41 +47,52 @@ class InvenTreeCompany extends InvenTreeModel {
"is_customer": {}, "is_customer": {},
"currency": {}, "currency": {},
}; };
if (InvenTreeAPI().supportsCompanyActiveStatus) {
fields["active"] = {};
}
return fields;
} }
String get image => (jsondata["image"] ?? jsondata["thumbnail"] ?? InvenTreeAPI.staticImage) as String; String get image =>
(jsondata["image"] ?? jsondata["thumbnail"] ?? InvenTreeAPI.staticImage)
as String;
String get thumbnail => (jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb) as String; String get thumbnail =>
(jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb)
as String;
String get website => (jsondata["website"] ?? "") as String; String get website => getString("website");
String get phone => (jsondata["phone"] ?? "") as String; String get phone => getString("phone");
String get email => (jsondata["email"] ?? "") as String; String get email => getString("email");
bool get isSupplier => (jsondata["is_supplier"] ?? false) as bool; bool get isSupplier => getBool("is_supplier");
bool get isManufacturer => (jsondata["is_manufacturer"] ?? false) as bool; bool get isManufacturer => getBool("is_manufacturer");
bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; bool get isCustomer => getBool("is_customer");
int get partSuppliedCount => (jsondata["parts_supplied"] ?? 0) as int; bool get active => getBool("active", backup: true);
int get partManufacturedCount => (jsondata["parts_manufactured"] ?? 0) as int; int get partSuppliedCount => getInt("part_supplied");
int get partManufacturedCount => getInt("parts_manufactured");
// Request a list of purchase orders against this company // Request a list of purchase orders against this company
Future<List<InvenTreePurchaseOrder>> getPurchaseOrders({bool? outstanding}) async { Future<List<InvenTreePurchaseOrder>> getPurchaseOrders({
bool? outstanding,
Map<String, String> filters = { }) async {
"supplier": "${pk}" Map<String, String> filters = {"supplier": "${pk}"};
};
if (outstanding != null) { if (outstanding != null) {
filters["outstanding"] = outstanding ? "true" : "false"; filters["outstanding"] = outstanding ? "true" : "false";
} }
final List<InvenTreeModel> results = await InvenTreePurchaseOrder().list( final List<InvenTreeModel> results = await InvenTreePurchaseOrder().list(
filters: filters filters: filters,
); );
List<InvenTreePurchaseOrder> orders = []; List<InvenTreePurchaseOrder> orders = [];
@ -78,103 +107,189 @@ class InvenTreeCompany extends InvenTreeModel {
} }
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
var company = InvenTreeCompany.fromJson(json); InvenTreeCompany.fromJson(json);
return company;
}
} }
/* /*
* The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database * The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database
*/ */
class InvenTreeSupplierPart extends InvenTreeModel { class InvenTreeSupplierPart extends InvenTreeModel {
InvenTreeSupplierPart() : super(); InvenTreeSupplierPart() : super();
InvenTreeSupplierPart.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeSupplierPart.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
String get URL => "company/part/"; String get URL => "company/part/";
Map<String, String> _filters() { static const String MODEL_TYPE = "supplierpart";
@override
List<String> get rolesRequired => ["part", "purchase_order"];
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => SupplierPartDetailWidget(this)),
);
}
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"supplier": {},
"SKU": {},
"link": {},
"note": {},
"packaging": {},
};
// At some point, pack_size was changed to pack_quantity
if (InvenTreeAPI().apiVersion < 117) {
fields["pack_size"] = {};
} else {
fields["pack_quantity"] = {};
}
if (InvenTreeAPI().supportsCompanyActiveStatus) {
fields["active"] = {};
}
return fields;
}
@override
Map<String, String> defaultFilters() {
return { return {
"manufacturer_detail": "true", "manufacturer_detail": "true",
"supplier_detail": "true", "supplier_detail": "true",
"manufacturer_part_detail": "true", "part_detail": "true",
}; };
} }
@override int get manufacturerId => getInt("pk", subKey: "manufacturer_detail");
Map<String, String> defaultListFilters() {
return _filters(); String get manufacturerName =>
getString("name", subKey: "manufacturer_detail");
String get MPN => getString("MPN", subKey: "manufacturer_part_detail");
String get manufacturerImage =>
(jsondata["manufacturer_detail"]?["image"] ??
jsondata["manufacturer_detail"]?["thumbnail"] ??
InvenTreeAPI.staticThumb)
as String;
int get manufacturerPartId => getInt("manufacturer_part");
int get supplierId => getInt("supplier");
String get supplierName => getString("name", subKey: "supplier_detail");
String get supplierImage =>
(jsondata["supplier_detail"]?["image"] ??
jsondata["supplier_detail"]?["thumbnail"] ??
InvenTreeAPI.staticThumb)
as String;
String get SKU => getString("SKU");
bool get active => getBool("active", backup: true);
int get partId => getInt("part");
double get inStock => getDouble("in_stock");
double get onOrder => getDouble("on_order");
String get partImage =>
(jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb)
as String;
String get partName => getString("name", subKey: "part_detail");
Map<String, dynamic> get partDetail => getMap("part_detail");
String get partDescription => getString("description", subKey: "part_detail");
String get note => getString("note");
String get packaging => getString("packaging");
String get pack_quantity {
if (InvenTreeAPI().apiVersion < 117) {
return getString("pack_size");
} else {
return getString("pack_quantity");
}
} }
@override @override
Map<String, String> defaultGetFilters() { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
return _filters(); InvenTreeSupplierPart.fromJson(json);
}
int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int;
String get manufacturerName => (jsondata["manufacturer_detail"]["name"] ?? "") as String;
String get manufacturerImage => (jsondata["manufacturer_detail"]["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
int get manufacturerPartId => (jsondata["manufacturer_part"] ?? -1) as int;
int get supplierId => (jsondata["supplier"] ?? -1) as int;
String get supplierName => (jsondata["supplier_detail"]["name"] ?? "") as String;
String get supplierImage => (jsondata["supplier_detail"]["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
String get SKU => (jsondata["SKU"] ?? "") as String;
String get MPN => (jsondata["MPN"] ?? "") as String;
int get partId => (jsondata["part"] ?? -1) as int;
String get partImage => (jsondata["part_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String;
String get partName => (jsondata["part_detail"]["full_name"] ?? "") as String;
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) {
var part = InvenTreeSupplierPart.fromJson(json);
return part;
}
} }
class InvenTreeManufacturerPart extends InvenTreeModel { class InvenTreeManufacturerPart extends InvenTreeModel {
InvenTreeManufacturerPart() : super(); InvenTreeManufacturerPart() : super();
InvenTreeManufacturerPart.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeManufacturerPart.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
String url = "company/part/manufacturer/"; String URL = "company/part/manufacturer/";
static const String MODEL_TYPE = "manufacturerpart";
@override @override
Map<String, String> defaultListFilters() { List<String> get rolesRequired => ["part"];
return {
"manufacturer_detail": "true", @override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"manufacturer": {},
"MPN": {},
"link": {},
}; };
return fields;
} }
int get partId => (jsondata["part"] ?? -1) as int;
int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int;
String get MPN => (jsondata["MPN"] ?? "") as String;
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { Map<String, String> defaultFilters() {
var part = InvenTreeManufacturerPart.fromJson(json); return {"manufacturer_detail": "true", "part_detail": "true"};
return part;
} }
int get partId => getInt("part");
String get partName => getString("name", subKey: "part_detail");
String get partDescription => getString("description", subKey: "part_detail");
String get partIPN => getString("IPN", subKey: "part_detail");
String get partImage =>
(jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb)
as String;
int get manufacturerId => getInt("manufacturer");
String get manufacturerName =>
getString("name", subKey: "manufacturer_detail");
String get manufacturerDescription =>
getString("description", subKey: "manufacturer_detail");
String get manufacturerImage =>
(jsondata["manufacturer_detail"]?["image"] ??
jsondata["manufacturer_detail"]?["thumbnail"] ??
InvenTreeAPI.staticThumb)
as String;
String get MPN => getString("MPN");
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeManufacturerPart.fromJson(json);
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,48 @@
import "package:inventree/inventree/model.dart";
/*
* Class representing a "notification"
*/
class InvenTreeNotification extends InvenTreeModel {
InvenTreeNotification() : super();
InvenTreeNotification.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeNotification createFromJson(Map<String, dynamic> json) {
return InvenTreeNotification.fromJson(json);
}
@override
String get URL => "notifications/";
@override
Map<String, String> defaultListFilters() {
// By default, only return 'unread' notifications
return {"read": "false"};
}
String get message => getString("message");
DateTime? get creationDate {
if (jsondata.containsKey("creation")) {
return DateTime.tryParse((jsondata["creation"] ?? "") as String);
} else {
return null;
}
}
/*
* Dismiss this notification (mark as read)
*/
Future<void> dismiss() async {
if (api.apiVersion >= 82) {
// "Modern" API endpoint operates a little differently
await update(values: {"read": "true"});
} else {
await api.post("${url}read/");
}
}
}

161
lib/inventree/orders.dart Normal file
View file

@ -0,0 +1,161 @@
/*
* Base model for various "orders" which share common properties
*/
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.dart";
/*
* Generic class representing an "order"
*/
class InvenTreeOrder extends InvenTreeModel {
InvenTreeOrder() : super();
InvenTreeOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json);
String get issueDate => getString("issue_date");
String get startDate => getString("start_date");
String get completionDate => getDateString("complete_date");
String get creationDate => getDateString("creation_date");
String get shipmentDate => getDateString("shipment_date");
String get targetDate => getDateString("target_date");
int get lineItemCount => getInt("line_items", backup: 0);
int get completedLineItemCount => getInt("completed_lines", backup: 0);
int get shipmentCount => getInt("shipments_count", backup: 0);
int get completedShipmentCount =>
getInt("completed_shipments_count", backup: 0);
bool get complete => completedLineItemCount >= lineItemCount;
bool get overdue => getBool("overdue");
String get reference => getString("reference");
int get responsibleId => getInt("responsible");
String get responsibleName => getString("name", subKey: "responsible_detail");
String get responsibleLabel =>
getString("label", subKey: "responsible_detail");
// Project code information
int get projectCodeId => getInt("project_code");
String get projectCode => getString("code", subKey: "project_code_detail");
String get projectCodeDescription =>
getString("description", subKey: "project_code_detail");
bool get hasProjectCode => projectCode.isNotEmpty;
double? get totalPrice {
String price = getString("total_price");
if (price.isEmpty) {
return null;
} else {
return double.tryParse(price);
}
}
// Return the currency for this order
// Note that the nomenclature in the API changed at some point
String get totalPriceCurrency {
if (jsondata.containsKey("order_currency")) {
return getString("order_currency");
} else if (jsondata.containsKey("total_price_currency")) {
return getString("total_price_currency");
} else {
return "";
}
}
}
/*
* Generic class representing an "order line"
*/
class InvenTreeOrderLine extends InvenTreeModel {
InvenTreeOrderLine() : super();
InvenTreeOrderLine.fromJson(Map<String, dynamic> json) : super.fromJson(json);
bool get overdue => getBool("overdue");
double get quantity => getDouble("quantity");
String get reference => getString("reference");
int get orderId => getInt("order");
InvenTreePart? get part {
dynamic part_detail = jsondata["part_detail"];
if (part_detail == null) {
return null;
} else {
return InvenTreePart.fromJson(part_detail as Map<String, dynamic>);
}
}
int get partId => getInt("pk", subKey: "part_detail");
String get partName => getString("name", subKey: "part_detail");
String get partImage {
String img = getString("thumbnail", subKey: "part_detail");
if (img.isEmpty) {
img = getString("image", subKey: "part_detail");
}
return img;
}
String get targetDate => getDateString("target_date");
}
/*
* Generic class representing an "ExtraLineItem"
*/
class InvenTreeExtraLineItem extends InvenTreeModel {
InvenTreeExtraLineItem() : super();
InvenTreeExtraLineItem.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
int get orderId => getInt("order");
double get quantity => getDouble("quantity");
String get reference => getString("reference");
double get price => getDouble("price");
String get priceCurrency => getString("price_currency");
@override
Map<String, Map<String, dynamic>> formFields() {
return {
"order": {
// The order cannot be edited
"hidden": true,
},
"reference": {},
"description": {},
"quantity": {},
"price": {},
"price_currency": {},
"link": {},
"notes": {},
};
}
}

View file

@ -0,0 +1,77 @@
import "package:inventree/inventree/model.dart";
class InvenTreeParameter extends InvenTreeModel {
InvenTreeParameter() : super();
InvenTreeParameter.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override
InvenTreeParameter createFromJson(Map<String, dynamic> json) =>
InvenTreeParameter.fromJson(json);
@override
String get URL => "parameter/";
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"header": {
"type": "string",
"read_only": true,
"label": name,
"help_text": description,
"value": "",
},
"data": {"type": "string"},
"note": {},
};
return fields;
}
@override
String get name => getString("name", subKey: "template_detail");
@override
String get description => getString("description", subKey: "template_detail");
String get value => getString("data");
String get valueString {
String v = value;
if (units.isNotEmpty) {
v += " ";
v += units;
}
return v;
}
bool get as_bool => value.toLowerCase() == "true";
String get units => getString("units", subKey: "template_detail");
bool get is_checkbox =>
getBool("checkbox", subKey: "template_detail", backup: false);
// The model type of the instance this attachment is associated with
String get modelType => getString("model_type");
// The ID of the instance this attachment is associated with
int get modelId => getInt("model_id");
// Return a count of how many parameters exist against the specified model ID
Future<int> countParameters(String modelType, int modelId) async {
Map<String, String> filters = {};
if (!api.supportsModernParameters) {
return 0;
}
filters["model_type"] = modelType;
filters["model_id"] = modelId.toString();
return count(filters: filters);
}
}

View file

@ -1,47 +1,61 @@
import "dart:io"; import "dart:io";
import "dart:math";
import "package:flutter/material.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/sentry.dart";
import "package:inventree/inventree/company.dart";
import "package:flutter/material.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
import "package:inventree/widget/part/category_display.dart";
import "package:inventree/widget/part/part_detail.dart";
/*
* Class representing the PartCategory database model
*/
class InvenTreePartCategory extends InvenTreeModel { class InvenTreePartCategory extends InvenTreeModel {
InvenTreePartCategory() : super(); InvenTreePartCategory() : super();
InvenTreePartCategory.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePartCategory.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
String get URL => "part/category/"; String get URL => "part/category/";
@override static const String MODEL_TYPE = "partcategory";
Map<String, dynamic> formFields() {
return { @override
List<String> get rolesRequired => ["part"];
// Navigate to a detail page for this item
@override
Future<Object?> goToDetailPage(BuildContext context) async {
// Default implementation does not do anything...
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => CategoryDisplayWidget(this)),
);
}
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"name": {}, "name": {},
"description": {}, "description": {},
"parent": {} "parent": {},
"structural": {},
}; };
return fields;
} }
@override String get pathstring => getString("pathstring");
Map<String, String> defaultListFilters() {
return { String get parentPathString {
"active": "true",
"cascade": "false"
};
}
String get pathstring => (jsondata["pathstring"] ?? "") as String;
String get parentpathstring {
// TODO - Drive the refactor tractor through this
List<String> psplit = pathstring.split("/"); List<String> psplit = pathstring.split("/");
if (psplit.isNotEmpty) { if (psplit.isNotEmpty) {
@ -57,47 +71,45 @@ class InvenTreePartCategory extends InvenTreeModel {
return p; return p;
} }
int get partcount => (jsondata["parts"] ?? 0) as int; // Return the number of parts in this category
// Note that the API changed from 'parts' to 'part_count' (v69)
int get partcount =>
(jsondata["part_count"] ?? jsondata["parts"] ?? 0) as int;
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
var cat = InvenTreePartCategory.fromJson(json); InvenTreePartCategory.fromJson(json);
// TODO ?
return cat;
}
} }
/*
* Class representing the PartTestTemplate database model
*/
class InvenTreePartTestTemplate extends InvenTreeModel { class InvenTreePartTestTemplate extends InvenTreeModel {
InvenTreePartTestTemplate() : super(); InvenTreePartTestTemplate() : super();
InvenTreePartTestTemplate.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePartTestTemplate.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
String get URL => "part/test-template/"; String get URL => "part/test-template/";
String get key => (jsondata["key"] ?? "") as String; static const String MODEL_TYPE = "parttesttemplate";
String get testName => (jsondata["test_name"] ?? "") as String; String get key => getString("key");
bool get required => (jsondata["required"] ?? false) as bool; String get testName => getString("test_name");
bool get requiresValue => (jsondata["requires_value"] ?? false) as bool; bool get required => getBool("required");
bool get requiresAttachment => (jsondata["requires_attachment"] ?? false) as bool; bool get requiresValue => getBool("requires_value");
bool get requiresAttachment => getBool("requires_attachment");
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
var template = InvenTreePartTestTemplate.fromJson(json); InvenTreePartTestTemplate.fromJson(json);
return template;
}
bool passFailStatus() { bool passFailStatus() {
var result = latestResult(); var result = latestResult();
if (result == null) { if (result == null) {
@ -118,12 +130,12 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
return results.last; return results.last;
} }
} }
/*
* Class representing the Part database model
*/
class InvenTreePart extends InvenTreeModel { class InvenTreePart extends InvenTreeModel {
InvenTreePart() : super(); InvenTreePart() : super();
InvenTreePart.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePart.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@ -131,8 +143,23 @@ class InvenTreePart extends InvenTreeModel {
@override @override
String get URL => "part/"; String get URL => "part/";
static const String MODEL_TYPE = "part";
@override @override
Map<String, dynamic> formFields() { List<String> get rolesRequired => ["part"];
// Navigate to a detail page for this item
@override
Future<Object?> goToDetailPage(BuildContext context) async {
// Default implementation does not do anything...
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => PartDetailWidget(this)),
);
}
@override
Map<String, Map<String, dynamic>> formFields() {
return { return {
"name": {}, "name": {},
"description": {}, "description": {},
@ -160,18 +187,8 @@ class InvenTreePart extends InvenTreeModel {
} }
@override @override
Map<String, String> defaultListFilters() { Map<String, String> defaultFilters() {
return { return {"category_detail": "true"};
"cascade": "false",
"active": "true",
};
}
@override
Map<String, String> defaultGetFilters() {
return {
"category_detail": "true", // Include category detail information
};
} }
// Cached list of stock items // Cached list of stock items
@ -180,34 +197,50 @@ class InvenTreePart extends InvenTreeModel {
int get stockItemCount => stockItems.length; int get stockItemCount => stockItems.length;
// Request stock items for this part // Request stock items for this part
Future<void> getStockItems(BuildContext context, {bool showDialog=false}) async { Future<void> getStockItems(
BuildContext context, {
bool showDialog = false,
}) async {
await InvenTreeStockItem()
.list(filters: {"part": "${pk}", "in_stock": "true"})
.then((var items) {
stockItems.clear();
await InvenTreeStockItem().list( for (var item in items) {
filters: { if (item is InvenTreeStockItem) {
"part": "${pk}", stockItems.add(item);
"in_stock": "true", }
}, }
).then((var items) { });
stockItems.clear();
for (var item in items) {
if (item is InvenTreeStockItem) {
stockItems.add(item);
}
}
});
} }
int get supplierCount => (jsondata["suppliers"] ?? 0) as int; // Request pricing data for this part
Future<InvenTreePartPricing?> getPricing() async {
try {
final response = await InvenTreeAPI().get("/api/part/${pk}/pricing/");
if (response.isValid()) {
final pricingData = response.data;
if (pricingData is Map<String, dynamic>) {
return InvenTreePartPricing.fromJson(pricingData);
}
}
} catch (e, stackTrace) {
print("Exception while fetching pricing data for part $pk: $e");
sentryReportError("getPricing", e, stackTrace);
}
return null;
}
int get supplierCount => getInt("suppliers", backup: 0);
// Request supplier parts for this part // Request supplier parts for this part
Future<List<InvenTreeSupplierPart>> getSupplierParts() async { Future<List<InvenTreeSupplierPart>> getSupplierParts() async {
List<InvenTreeSupplierPart> _supplierParts = []; List<InvenTreeSupplierPart> _supplierParts = [];
final parts = await InvenTreeSupplierPart().list( final parts = await InvenTreeSupplierPart().list(
filters: { filters: {"part": "${pk}"},
"part": "${pk}",
}
); );
for (var result in parts) { for (var result in parts) {
@ -219,7 +252,6 @@ class InvenTreePart extends InvenTreeModel {
return _supplierParts; return _supplierParts;
} }
// Cached list of test templates // Cached list of test templates
List<InvenTreePartTestTemplate> testingTemplates = []; List<InvenTreePartTestTemplate> testingTemplates = [];
@ -227,13 +259,9 @@ class InvenTreePart extends InvenTreeModel {
// Request test templates from the serve // Request test templates from the serve
Future<void> getTestTemplates() async { Future<void> getTestTemplates() async {
InvenTreePartTestTemplate().list(filters: {"part": "${pk}"}).then((
InvenTreePartTestTemplate().list( var templates,
filters: { ) {
"part": "${pk}",
},
).then((var templates) {
testingTemplates.clear(); testingTemplates.clear();
for (var t in templates) { for (var t in templates) {
@ -246,196 +274,214 @@ class InvenTreePart extends InvenTreeModel {
int? get defaultLocation => jsondata["default_location"] as int?; int? get defaultLocation => jsondata["default_location"] as int?;
// Get the number of stock on order for this Part double get onOrder => getDouble("ordering");
double get onOrder => double.tryParse(jsondata["ordering"].toString()) ?? 0;
String get onOrderString { String get onOrderString => simpleNumberString(onOrder);
return simpleNumberString(onOrder); double get inStock {
if (jsondata.containsKey("total_in_stock")) {
return getDouble("total_in_stock");
} else {
return getDouble("in_stock");
}
}
String get inStockString => simpleNumberString(inStock);
// Get the 'available stock' for this Part
double get unallocatedStock {
double unallocated = 0;
// Note that the 'available_stock' was not added until API v35
if (jsondata.containsKey("unallocated_stock")) {
unallocated =
double.tryParse(jsondata["unallocated_stock"].toString()) ?? 0;
} else {
unallocated = inStock;
} }
// Get the stock count for this Part return max(0, unallocated);
double get inStock => double.tryParse(jsondata["in_stock"].toString()) ?? 0; }
String get inStockString { String get unallocatedStockString => simpleNumberString(unallocatedStock);
String q = simpleNumberString(inStock); String stockString({bool includeUnits = true}) {
String q = unallocatedStockString;
if (units.isNotEmpty) { if (unallocatedStock != inStock) {
q += " ${units}"; q += " / ${inStockString}";
}
return q;
} }
// Get the 'available stock' for this Part if (includeUnits && units.isNotEmpty) {
double get unallocatedStock { q += " ${units}";
// Note that the 'available_stock' was not added until API v35
if (jsondata.containsKey("unallocated_stock")) {
return double.tryParse(jsondata["unallocated_stock"].toString()) ?? 0;
} else {
return inStock;
}
} }
String get unallocatedStockString { return q;
String q = simpleNumberString(unallocatedStock); }
if (units.isNotEmpty) { String get units => getString("units");
q += " ${units}";
}
return q; // Get the ID of the Part that this part is a variant of (or null)
} int? get variantOf => jsondata["variant_of"] as int?;
String stockString({bool includeUnits = true}) { // Get the number of units being build for this Part
String q = unallocatedStockString; double get building => getDouble("building");
if (unallocatedStock != inStock) { // Get the number of BOMs this Part is used in (if it is a component)
q += " / ${inStockString}"; int get usedInCount =>
} jsondata.containsKey("used_in") ? getInt("used_in", backup: 0) : 0;
if (includeUnits && units.isNotEmpty) { bool get isAssembly => getBool("assembly");
q += " ${units}";
}
return q; bool get isComponent => getBool("component");
}
String get units => (jsondata["units"] ?? "") as String; bool get isPurchaseable => getBool("purchaseable");
// Get the ID of the Part that this part is a variant of (or null) bool get isSalable => getBool("salable");
int? get variantOf => jsondata["variant_of"] as int?;
// Get the number of units being build for this Part bool get isActive => getBool("active");
double get building => double.tryParse(jsondata["building"].toString()) ?? 0;
// Get the number of BOM items in this Part (if it is an assembly) bool get isVirtual => getBool("virtual");
int get bomItemCount => (jsondata["bom_items"] ?? 0) as int;
// Get the number of BOMs this Part is used in (if it is a component) bool get isTemplate => getBool("is_template");
int get usedInCount => (jsondata["used_in"] ?? 0) as int;
bool get isAssembly => (jsondata["assembly"] ?? false) as bool; bool get isTrackable => getBool("trackable");
bool get isComponent => (jsondata["component"] ?? false) as bool; bool get isTestable => getBool("testable");
bool get isPurchaseable => (jsondata["purchaseable"] ?? false) as bool; // Get the IPN (internal part number) for the Part instance
String get IPN => getString("IPN");
bool get isSalable => (jsondata["salable"] ?? false) as bool; // Get the revision string for the Part instance
String get revision => getString("revision");
bool get isActive => (jsondata["active"] ?? false) as bool; // Get the category ID for the Part instance (or "null" if does not exist)
int get categoryId => getInt("category");
bool get isVirtual => (jsondata["virtual"] ?? false) as bool; // Get the category name for the Part instance
String get categoryName {
// Inavlid category ID
if (categoryId <= 0) return "";
bool get isTrackable => (jsondata["trackable"] ?? false) as bool; if (!jsondata.containsKey("category_detail")) return "";
// Get the IPN (internal part number) for the Part instance return (jsondata["category_detail"]?["name"] ?? "") as String;
String get IPN => (jsondata["IPN"] ?? "") as String; }
// Get the revision string for the Part instance // Get the category description for the Part instance
String get revision => (jsondata["revision"] ?? "") as String; String get categoryDescription {
// Invalid category ID
if (categoryId <= 0) return "";
// Get the category ID for the Part instance (or "null" if does not exist) if (!jsondata.containsKey("category_detail")) return "";
int get categoryId => (jsondata["category"] ?? -1) as int;
// Get the category name for the Part instance return (jsondata["category_detail"]?["description"] ?? "") as String;
String get categoryName { }
// Inavlid category ID
if (categoryId <= 0) return "";
if (!jsondata.containsKey("category_detail")) return ""; // Get the image URL for the Part instance
String get _image => getString("image");
return (jsondata["category_detail"]?["name"] ?? "") as String; // Get the thumbnail URL for the Part instance
} String get _thumbnail => getString("thumbnail");
// Get the category description for the Part instance // Return the fully-qualified name for the Part instance
String get categoryDescription { String get fullname {
// Invalid category ID String fn = getString("full_name");
if (categoryId <= 0) return "";
if (!jsondata.containsKey("category_detail")) return ""; if (fn.isNotEmpty) return fn;
return (jsondata["category_detail"]?["description"] ?? "") as String; List<String> elements = [];
}
// Get the image URL for the Part instance
String get _image => (jsondata["image"] ?? "") as String;
// Get the thumbnail URL for the Part instance if (IPN.isNotEmpty) elements.add(IPN);
String get _thumbnail => (jsondata["thumbnail"] ?? "") as String;
// Return the fully-qualified name for the Part instance elements.add(name);
String get fullname {
String fn = (jsondata["full_name"] ?? "") as String; if (revision.isNotEmpty) elements.add(revision);
if (fn.isNotEmpty) return fn; return elements.join(" | ");
}
List<String> elements = []; // Return a path to the image for this Part
String get image {
// Use thumbnail as a backup
String img = _image.isNotEmpty ? _image : _thumbnail;
if (IPN.isNotEmpty) elements.add(IPN); return img.isNotEmpty ? img : InvenTreeAPI.staticImage;
}
elements.add(name); // Return a path to the thumbnail for this part
String get thumbnail {
// Use image as a backup
String img = _thumbnail.isNotEmpty ? _thumbnail : _image;
if (revision.isNotEmpty) elements.add(revision); return img.isNotEmpty ? img : InvenTreeAPI.staticThumb;
}
return elements.join(" | "); Future<bool> uploadImage(File image) async {
} // Upload file against this part
final APIResponse response = await InvenTreeAPI().uploadFile(
url,
image,
method: "PATCH",
name: "image",
);
// Return a path to the image for this Part return response.successful();
String get image { }
// Use thumbnail as a backup
String img = _image.isNotEmpty ? _image : _thumbnail;
return img.isNotEmpty ? img : InvenTreeAPI.staticImage; // Return the "starred" status of this part
} bool get starred => getBool("starred");
// Return a path to the thumbnail for this part
String get thumbnail {
// Use image as a backup
String img = _thumbnail.isNotEmpty ? _thumbnail : _image;
return img.isNotEmpty ? img : InvenTreeAPI.staticThumb;
}
Future<bool> uploadImage(File image) async {
// Upload file against this part
final APIResponse response = await InvenTreeAPI().uploadFile(
url,
image,
method: "PATCH",
name: "image",
);
return response.successful();
}
// Return the "starred" status of this part
bool get starred => (jsondata["starred"] ?? false) as bool;
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreePart.fromJson(json);
var part = InvenTreePart.fromJson(json);
return part;
}
} }
class InvenTreePartPricing extends InvenTreeModel {
InvenTreePartPricing() : super();
class InvenTreePartAttachment extends InvenTreeAttachment { InvenTreePartPricing.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
InvenTreePartAttachment() : super();
InvenTreePartAttachment.fromJson(Map<String, dynamic> json) : super.fromJson(json);
@override @override
String get URL => "part/attachment/"; List<String> get rolesRequired => ["part"];
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
return InvenTreePartAttachment.fromJson(json); InvenTreePartPricing.fromJson(json);
}
} // Price data accessors
String get currency => getString("currency", backup: "USD");
double? get overallMin => getDoubleOrNull("overall_min");
double? get overallMax => getDoubleOrNull("overall_max");
double? get overrideMin => getDoubleOrNull("override_min");
double? get overrideMax => getDoubleOrNull("override_max");
String get overrideMinCurrency =>
getString("override_min_currency", backup: currency);
String get overrideMaxCurrency =>
getString("override_max_currency", backup: currency);
double? get bomCostMin => getDoubleOrNull("bom_cost_min");
double? get bomCostMax => getDoubleOrNull("bom_cost_max");
double? get purchaseCostMin => getDoubleOrNull("purchase_cost_min");
double? get purchaseCostMax => getDoubleOrNull("purchase_cost_max");
double? get internalCostMin => getDoubleOrNull("internal_cost_min");
double? get internalCostMax => getDoubleOrNull("internal_cost_max");
double? get supplierPriceMin => getDoubleOrNull("supplier_price_min");
double? get supplierPriceMax => getDoubleOrNull("supplier_price_max");
double? get variantCostMin => getDoubleOrNull("variant_cost_min");
double? get variantCostMax => getDoubleOrNull("variant_cost_max");
double? get salePriceMin => getDoubleOrNull("sale_price_min");
double? get salePriceMax => getDoubleOrNull("sale_price_max");
double? get saleHistoryMin => getDoubleOrNull("sale_history_min");
double? get saleHistoryMax => getDoubleOrNull("sale_history_max");
}

View file

@ -0,0 +1,27 @@
import "package:inventree/inventree/model.dart";
/*
* Class representing the ProjectCode database model
*/
class InvenTreeProjectCode extends InvenTreeModel {
InvenTreeProjectCode() : super();
InvenTreeProjectCode.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeProjectCode.fromJson(json);
@override
String get URL => "project-code/";
static const String MODEL_TYPE = "projectcode";
@override
Map<String, Map<String, dynamic>> formFields() {
return {"code": {}, "description": {}};
}
String get code => getString("code");
}

View file

@ -1,73 +1,91 @@
import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/api.dart";
import "package:inventree/helpers.dart";
import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/orders.dart";
import "package:inventree/widget/order/extra_line_detail.dart";
import "package:inventree/widget/order/purchase_order_detail.dart";
import "package:inventree/widget/progress.dart";
// TODO: In the future, status codes should be retrieved from the server import "package:inventree/api_form.dart";
const int PO_STATUS_PENDING = 10; import "package:inventree/l10.dart";
const int PO_STATUS_PLACED = 20;
const int PO_STATUS_COMPLETE = 30;
const int PO_STATUS_CANCELLED = 40;
const int PO_STATUS_LOST = 50;
const int PO_STATUS_RETURNED = 60;
class InvenTreePurchaseOrder extends InvenTreeModel {
/*
* Class representing an individual PurchaseOrder instance
*/
class InvenTreePurchaseOrder extends InvenTreeOrder {
InvenTreePurchaseOrder() : super(); InvenTreePurchaseOrder() : super();
InvenTreePurchaseOrder.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePurchaseOrder.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreePurchaseOrder.fromJson(json);
@override @override
String get URL => "order/po/"; String get URL => "order/po/";
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => PurchaseOrderDetailWidget(this)),
);
}
static const String MODEL_TYPE = "purchaseorder";
@override
List<String> get rolesRequired => ["purchase_order"];
String get receive_url => "${url}receive/"; String get receive_url => "${url}receive/";
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { Map<String, Map<String, dynamic>> fields = {
"reference": {}, "reference": {},
"supplier": {
"filters": {"is_supplier": true},
},
"supplier_reference": {}, "supplier_reference": {},
"description": {}, "description": {},
"project_code": {},
"destination": {},
"start_date": {},
"target_date": {}, "target_date": {},
"link": {}, "link": {},
"responsible": {}, "responsible": {},
"contact": {
"filters": {"company": supplierId},
},
}; };
if (!InvenTreeAPI().supportsProjectCodes) {
fields.remove("project_code");
}
if (!InvenTreeAPI().supportsPurchaseOrderDestination) {
fields.remove("destination");
}
if (!InvenTreeAPI().supportsStartDate) {
fields.remove("start_date");
}
return fields;
} }
@override @override
Map<String, String> defaultGetFilters() { Map<String, String> defaultFilters() {
return { return {"supplier_detail": "true"};
"supplier_detail": "true",
};
} }
@override int get supplierId => getInt("supplier");
Map<String, String> defaultListFilters() {
return {
"supplier_detail": "true",
};
}
String get issueDate => (jsondata["issue_date"] ?? "") as String;
String get completeDate => (jsondata["complete_date"] ?? "") as String;
String get creationDate => (jsondata["creation_date"] ?? "") as String;
String get targetDate => (jsondata["target_date"] ?? "") as String;
int get lineItemCount => (jsondata["line_items"] ?? 0) as int;
bool get overdue => (jsondata["overdue"] ?? false) as bool;
String get reference => (jsondata["reference"] ?? "") as String;
int get responsibleId => (jsondata["responsible"] ?? -1) as int;
int get supplierId => (jsondata["supplier"] ?? -1) as int;
InvenTreeCompany? get supplier { InvenTreeCompany? get supplier {
dynamic supplier_detail = jsondata["supplier_detail"]; dynamic supplier_detail = jsondata["supplier_detail"];
if (supplier_detail == null) { if (supplier_detail == null) {
@ -77,24 +95,30 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
} }
} }
String get supplierReference => (jsondata["supplier_reference"] ?? "") as String; String get supplierReference => getString("supplier_reference");
int get status => (jsondata["status"] ?? -1) as int; int get destinationId => getInt("destination");
String get statusText => (jsondata["status_text"] ?? "") as String; bool get isOpen => api.PurchaseOrderStatus.isNameIn(status, [
"PENDING",
"PLACED",
"ON_HOLD",
]);
bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; bool get isPending =>
api.PurchaseOrderStatus.isNameIn(status, ["PENDING", "ON_HOLD"]);
bool get isPlaced => status == PO_STATUS_PLACED; bool get isPlaced => api.PurchaseOrderStatus.isNameIn(status, ["PLACED"]);
bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED; bool get isFailed => api.PurchaseOrderStatus.isNameIn(status, [
"CANCELLED",
"LOST",
"RETURNED",
]);
Future<List<InvenTreePOLineItem>> getLineItems() async { Future<List<InvenTreePOLineItem>> getLineItems() async {
final results = await InvenTreePOLineItem().list( final results = await InvenTreePOLineItem().list(
filters: { filters: {"order": "${pk}"},
"order": "${pk}",
}
); );
List<InvenTreePOLineItem> items = []; List<InvenTreePOLineItem> items = [];
@ -108,77 +132,92 @@ class InvenTreePurchaseOrder extends InvenTreeModel {
return items; return items;
} }
@override /// Mark this order as "placed" / "issued"
InvenTreeModel createFromJson(Map<String, dynamic> json) { Future<void> issueOrder() async {
return InvenTreePurchaseOrder.fromJson(json); // Order can only be placed when the order is 'pending'
if (!isPending) {
return;
}
showLoadingOverlay();
await api.post("${url}issue/", expectedStatusCode: 201);
hideLoadingOverlay();
}
/// Mark this order as "cancelled"
Future<void> cancelOrder() async {
if (!isOpen) {
return;
}
showLoadingOverlay();
await api.post("${url}cancel/", expectedStatusCode: 201);
hideLoadingOverlay();
} }
} }
class InvenTreePOLineItem extends InvenTreeModel { class InvenTreePOLineItem extends InvenTreeOrderLine {
InvenTreePOLineItem() : super(); InvenTreePOLineItem() : super();
InvenTreePOLineItem.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreePOLineItem.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreePOLineItem.fromJson(json);
@override @override
String get URL => "order/po-line/"; String get URL => "order/po-line/";
@override @override
Map<String, dynamic> formFields() { List<String> get rolesRequired => ["purchase_order"];
@override
Map<String, Map<String, dynamic>> formFields() {
return { return {
// TODO: @Guusggg Not sure what will come here. "part": {
// "quantity": {}, // We cannot edit the supplier part field here
// "reference": {}, "hidden": true,
// "notes": {}, },
// "order": {}, "order": {
// "part": {}, // We cannot edit the order field here
"received": {}, "hidden": true,
// "purchase_price": {}, },
// "purchase_price_currency": {}, "reference": {},
// "destination": {} "quantity": {},
"purchase_price": {},
"purchase_price_currency": {},
"destination": {},
"notes": {},
"link": {},
}; };
} }
@override @override
Map<String, String> defaultGetFilters() { Map<String, String> defaultFilters() {
return { return {"part_detail": "true", "order_detail": "true"};
"part_detail": "true",
};
} }
@override double get received => getDouble("received");
Map<String, String> defaultListFilters() {
return {
"part_detail": "true",
};
}
bool get isComplete => received >= quantity; bool get isComplete => received >= quantity;
double get quantity => (jsondata["quantity"] ?? 0) as double; double get progressRatio {
if (quantity <= 0 || received <= 0) {
return 0;
}
double get received => (jsondata["received"] ?? 0) as double; return received / quantity;
}
String get progressString =>
simpleNumberString(received) + " / " + simpleNumberString(quantity);
double get outstanding => quantity - received; double get outstanding => quantity - received;
String get reference => (jsondata["reference"] ?? "") as String; int get supplierPartId => getInt("part");
int get orderId => (jsondata["order"] ?? -1) as int;
int get supplierPartId => (jsondata["part"] ?? -1) as int;
InvenTreePart? get part {
dynamic part_detail = jsondata["part_detail"];
if (part_detail == null) {
return null;
} else {
return InvenTreePart.fromJson(part_detail as Map<String, dynamic>);
}
}
InvenTreeSupplierPart? get supplierPart { InvenTreeSupplierPart? get supplierPart {
dynamic detail = jsondata["supplier_part_detail"]; dynamic detail = jsondata["supplier_part_detail"];
if (detail == null) { if (detail == null) {
@ -188,18 +227,112 @@ class InvenTreePOLineItem extends InvenTreeModel {
} }
} }
double get purchasePrice => double.parse((jsondata["purchase_price"] ?? "") as String); InvenTreePurchaseOrder? get purchaseOrder {
dynamic detail = jsondata["order_detail"];
String get purchasePriceCurrency => (jsondata["purchase_price_currency"] ?? "") as String; if (detail == null) {
return null;
} else {
return InvenTreePurchaseOrder.fromJson(detail as Map<String, dynamic>);
}
}
String get purchasePriceString => (jsondata["purchase_price_string"] ?? "") as String; String get SKU => getString("SKU", subKey: "supplier_part_detail");
int get destination => (jsondata["destination"] ?? -1) as int; double get purchasePrice => getDouble("purchase_price");
Map<String, dynamic> get destinationDetail => (jsondata["destination_detail"] ?? {}) as Map<String, dynamic>; String get purchasePriceCurrency => getString("purchase_price_currency");
@override int get destinationId => getInt("destination");
InvenTreeModel createFromJson(Map<String, dynamic> json) {
return InvenTreePOLineItem.fromJson(json); Map<String, dynamic> get orderDetail => getMap("order_detail");
Map<String, dynamic> get destinationDetail => getMap("destination_detail");
// Receive this line item into stock
Future<void> receive(
BuildContext context, {
int? destination,
double? quantity,
String? barcode,
Function? onSuccess,
}) async {
// Infer the destination location from the line item if not provided
if (destinationId > 0) {
destination = destinationId;
}
destination ??= (orderDetail["destination"]) as int?;
quantity ??= outstanding;
// Construct form fields
Map<String, dynamic> fields = {
"line_item": {
"parent": "items",
"nested": true,
"hidden": true,
"value": pk,
},
"quantity": {"parent": "items", "nested": true, "value": quantity},
"location": {},
"status": {"parent": "items", "nested": true},
"batch_code": {"parent": "items", "nested": true},
"barcode": {
"parent": "items",
"nested": true,
"type": "barcode",
"label": L10().barcodeAssign,
"value": barcode,
"required": false,
},
};
if (destination != null && destination > 0) {
fields["location"]?["value"] = destination;
}
InvenTreePurchaseOrder? order = purchaseOrder;
if (order != null) {
await launchApiForm(
context,
L10().receiveItem,
order.receive_url,
fields,
method: "POST",
icon: TablerIcons.transition_right,
onSuccess: (data) {
if (onSuccess != null) {
onSuccess();
}
},
);
}
}
}
class InvenTreePOExtraLineItem extends InvenTreeExtraLineItem {
InvenTreePOExtraLineItem() : super();
InvenTreePOExtraLineItem.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreePOExtraLineItem.fromJson(json);
@override
String get URL => "order/po-extra-line/";
@override
List<String> get rolesRequired => ["purchase_order"];
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => ExtraLineDetailWidget(this)),
);
} }
} }

View file

@ -0,0 +1,430 @@
import "package:flutter/material.dart";
import "package:inventree/api.dart";
import "package:inventree/helpers.dart";
import "package:inventree/inventree/company.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/orders.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/stock.dart";
import "package:inventree/widget/order/so_shipment_detail.dart";
import "package:inventree/widget/progress.dart";
import "package:inventree/widget/order/extra_line_detail.dart";
import "package:inventree/widget/order/sales_order_detail.dart";
/*
* Class representing an individual SalesOrder
*/
class InvenTreeSalesOrder extends InvenTreeOrder {
InvenTreeSalesOrder() : super();
InvenTreeSalesOrder.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeSalesOrder.fromJson(json);
@override
String get URL => "order/so/";
static const String MODEL_TYPE = "salesorder";
@override
List<String> get rolesRequired => ["sales_order"];
String get allocate_url => "${url}allocate/";
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => SalesOrderDetailWidget(this)),
);
}
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"reference": {},
"customer": {
"filters": {"is_customer": true, "active": true},
},
"customer_reference": {},
"description": {},
"project_code": {},
"start_date": {},
"target_date": {},
"link": {},
"responsible": {},
"contact": {
"filters": {"company": customerId},
},
};
if (!InvenTreeAPI().supportsProjectCodes) {
fields.remove("project_code");
}
if (!InvenTreeAPI().supportsContactModel) {
fields.remove("contact");
}
if (!InvenTreeAPI().supportsStartDate) {
fields.remove("start_date");
}
return fields;
}
@override
Map<String, String> defaultFilters() {
return {"customer_detail": "true"};
}
Future<void> issueOrder() async {
if (!isPending) {
return;
}
showLoadingOverlay();
await api.post("${url}issue/", expectedStatusCode: 201);
hideLoadingOverlay();
}
/// Mark this order as "cancelled"
Future<void> cancelOrder() async {
if (!isOpen) {
return;
}
showLoadingOverlay();
await api.post("${url}cancel/", expectedStatusCode: 201);
hideLoadingOverlay();
}
int get customerId => getInt("customer");
InvenTreeCompany? get customer {
dynamic customer_detail = jsondata["customer_detail"];
if (customer_detail == null) {
return null;
} else {
return InvenTreeCompany.fromJson(customer_detail as Map<String, dynamic>);
}
}
String get customerReference => getString("customer_reference");
bool get isOpen => api.SalesOrderStatus.isNameIn(status, [
"PENDING",
"IN_PROGRESS",
"ON_HOLD",
]);
bool get isPending =>
api.SalesOrderStatus.isNameIn(status, ["PENDING", "ON_HOLD"]);
bool get isInProgress =>
api.SalesOrderStatus.isNameIn(status, ["IN_PROGRESS"]);
bool get isComplete => api.SalesOrderStatus.isNameIn(status, ["SHIPPED"]);
}
/*
* Class representing an individual line item in a SalesOrder
*/
class InvenTreeSOLineItem extends InvenTreeOrderLine {
InvenTreeSOLineItem() : super();
InvenTreeSOLineItem.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeSOLineItem.fromJson(json);
@override
String get URL => "order/so-line/";
@override
List<String> get rolesRequired => ["sales_order"];
@override
Map<String, Map<String, dynamic>> formFields() {
return {
"order": {"hidden": true},
"part": {
"filters": {"salable": true},
},
"quantity": {},
"reference": {},
"notes": {},
"link": {},
};
}
Map<String, Map<String, dynamic>> allocateFormFields() {
return {
"line_item": {"parent": "items", "nested": true, "hidden": true},
"stock_item": {"parent": "items", "nested": true, "filters": {}},
"quantity": {"parent": "items", "nested": true},
"shipment": {"filters": {}},
};
}
@override
Map<String, String> defaultFilters() {
return {"part_detail": "true"};
}
double get allocated => getDouble("allocated");
bool get isAllocated => allocated >= quantity;
double get allocatedRatio {
if (quantity <= 0 || allocated <= 0) {
return 0;
}
return allocated / quantity;
}
double get unallocatedQuantity {
double unallocated = quantity - allocated;
if (unallocated < 0) {
unallocated = 0;
}
return unallocated;
}
String get allocatedString =>
simpleNumberString(allocated) + " / " + simpleNumberString(quantity);
double get shipped => getDouble("shipped");
double get outstanding => quantity - shipped;
double get availableStock => getDouble("available_stock");
double get progressRatio {
if (quantity <= 0 || shipped <= 0) {
return 0;
}
return shipped / quantity;
}
String get progressString =>
simpleNumberString(shipped) + " / " + simpleNumberString(quantity);
bool get isComplete => shipped >= quantity;
double get available =>
getDouble("available_stock") + getDouble("available_variant_stock");
double get salePrice => getDouble("sale_price");
String get salePriceCurrency => getString("sale_price_currency");
}
class InvenTreeSOExtraLineItem extends InvenTreeExtraLineItem {
InvenTreeSOExtraLineItem() : super();
InvenTreeSOExtraLineItem.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeSOExtraLineItem.fromJson(json);
@override
String get URL => "order/so-extra-line/";
@override
List<String> get rolesRequired => ["sales_order"];
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => ExtraLineDetailWidget(this)),
);
}
}
/*
* Class representing a sales order shipment
*/
class InvenTreeSalesOrderShipment extends InvenTreeModel {
InvenTreeSalesOrderShipment() : super();
InvenTreeSalesOrderShipment.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeSalesOrderShipment.fromJson(json);
@override
String get URL => "/order/so/shipment/";
String get SHIP_SHIPMENT_URL => "/order/so/shipment/${pk}/ship/";
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => SOShipmentDetailWidget(this)),
);
}
@override
List<String> get rolesRequired => ["sales_order"];
static const String MODEL_TYPE = "salesordershipment";
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"order": {},
"reference": {},
"tracking_number": {},
"invoice_number": {},
"link": {},
};
return fields;
}
int get orderId => getInt("order");
InvenTreeSalesOrder? get order {
dynamic order_detail = jsondata["order_detail"];
if (order_detail == null) {
return null;
} else {
return InvenTreeSalesOrder.fromJson(order_detail as Map<String, dynamic>);
}
}
String get reference => getString("reference");
String get tracking_number => getString("tracking_number");
String get invoice_number => getString("invoice_number");
String? get shipment_date => getString("shipment_date");
String? get delivery_date => getString("delivery_date");
int? get checked_by_id => getInt("checked_by");
bool get isChecked => checked_by_id != null && checked_by_id! > 0;
bool get isShipped => shipment_date != null && shipment_date!.isNotEmpty;
bool get isDelivered => delivery_date != null && delivery_date!.isNotEmpty;
}
/*
* Class representing an allocation of stock against a SalesOrderShipment
*/
class InvenTreeSalesOrderAllocation extends InvenTreeModel {
InvenTreeSalesOrderAllocation() : super();
InvenTreeSalesOrderAllocation.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override
InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeSalesOrderAllocation.fromJson(json);
@override
String get URL => "/order/so-allocation/";
@override
List<String> get rolesRequired => ["sales_order"];
@override
Map<String, String> defaultFilters() {
return {
"part_detail": "true",
"order_detail": "true",
"item_detail": "true",
"location_detail": "true",
};
}
static const String MODEL_TYPE = "salesorderallocation";
int get orderId => getInt("order");
InvenTreeSalesOrder? get order {
dynamic order_detail = jsondata["order_detail"];
if (order_detail == null) {
return null;
} else {
return InvenTreeSalesOrder.fromJson(order_detail as Map<String, dynamic>);
}
}
int get stockItemId => getInt("item");
InvenTreeStockItem? get stockItem {
dynamic item_detail = jsondata["item_detail"];
if (item_detail == null) {
return null;
} else {
return InvenTreeStockItem.fromJson(item_detail as Map<String, dynamic>);
}
}
int get partId => getInt("part");
InvenTreePart? get part {
dynamic part_detail = jsondata["part_detail"];
if (part_detail == null) {
return null;
} else {
return InvenTreePart.fromJson(part_detail as Map<String, dynamic>);
}
}
int get shipmentId => getInt("shipment");
bool get hasShipment => shipmentId > 0;
InvenTreeSalesOrderShipment? get shipment {
dynamic shipment_detail = jsondata["shipment_detail"];
if (shipment_detail == null) {
return null;
} else {
return InvenTreeSalesOrderShipment.fromJson(
shipment_detail as Map<String, dynamic>,
);
}
}
int get locationId => getInt("location");
InvenTreeStockLocation? get location {
dynamic location_detail = jsondata["location_detail"];
if (location_detail == null) {
return null;
} else {
return InvenTreeStockLocation.fromJson(
location_detail as Map<String, dynamic>,
);
}
}
}

View file

@ -1,14 +1,16 @@
import "dart:io"; import "dart:io";
import "package:device_info_plus/device_info_plus.dart"; import "package:device_info_plus/device_info_plus.dart";
import "package:inventree/app_settings.dart"; import "package:inventree/helpers.dart";
import "package:one_context/one_context.dart";
import "package:package_info_plus/package_info_plus.dart"; import "package:package_info_plus/package_info_plus.dart";
import "package:sentry_flutter/sentry_flutter.dart"; import "package:sentry_flutter/sentry_flutter.dart";
import "package:inventree/api.dart"; import "package:inventree/api.dart";
import "package:inventree/dsn.dart";
import "package:inventree/preferences.dart";
Future<Map<String, dynamic>> getDeviceInfo() async { Future<Map<String, dynamic>> getDeviceInfo() async {
// Extract device information // Extract device information
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
@ -28,7 +30,6 @@ Future<Map<String, dynamic>> getDeviceInfo() async {
"identifierForVendor": iosDeviceInfo.identifierForVendor, "identifierForVendor": iosDeviceInfo.identifierForVendor,
"isPhysicalDevice": iosDeviceInfo.isPhysicalDevice, "isPhysicalDevice": iosDeviceInfo.isPhysicalDevice,
}; };
} else if (Platform.isAndroid) { } else if (Platform.isAndroid) {
final androidDeviceInfo = await deviceInfo.androidInfo; final androidDeviceInfo = await deviceInfo.androidInfo;
@ -37,13 +38,13 @@ Future<Map<String, dynamic>> getDeviceInfo() async {
"model": androidDeviceInfo.model, "model": androidDeviceInfo.model,
"device": androidDeviceInfo.device, "device": androidDeviceInfo.device,
"id": androidDeviceInfo.id, "id": androidDeviceInfo.id,
"androidId": androidDeviceInfo.androidId, "androidId": androidDeviceInfo.id,
"brand": androidDeviceInfo.brand, "brand": androidDeviceInfo.brand,
"display": androidDeviceInfo.display, "display": androidDeviceInfo.display,
"hardware": androidDeviceInfo.hardware, "hardware": androidDeviceInfo.hardware,
"manufacturer": androidDeviceInfo.manufacturer, "manufacturer": androidDeviceInfo.manufacturer,
"product": androidDeviceInfo.product, "product": androidDeviceInfo.product,
"version": androidDeviceInfo.version.release, "systemVersion": androidDeviceInfo.version.release,
"supported32BitAbis": androidDeviceInfo.supported32BitAbis, "supported32BitAbis": androidDeviceInfo.supported32BitAbis,
"supported64BitAbis": androidDeviceInfo.supported64BitAbis, "supported64BitAbis": androidDeviceInfo.supported64BitAbis,
"supportedAbis": androidDeviceInfo.supportedAbis, "supportedAbis": androidDeviceInfo.supportedAbis,
@ -54,12 +55,11 @@ Future<Map<String, dynamic>> getDeviceInfo() async {
return device_info; return device_info;
} }
Map<String, dynamic> getServerInfo() => { Map<String, dynamic> getServerInfo() => {
"version": InvenTreeAPI().version, "version": InvenTreeAPI().serverVersion,
"apiVersion": InvenTreeAPI().apiVersion,
}; };
Future<Map<String, dynamic>> getAppInfo() async { Future<Map<String, dynamic>> getAppInfo() async {
// Add app info // Add app info
final package_info = await PackageInfo.fromPlatform(); final package_info = await PackageInfo.fromPlatform();
@ -72,7 +72,6 @@ Future<Map<String, dynamic>> getAppInfo() async {
}; };
} }
bool isInDebugMode() { bool isInDebugMode() {
bool inDebugMode = false; bool inDebugMode = false;
@ -81,7 +80,13 @@ bool isInDebugMode() {
return inDebugMode; return inDebugMode;
} }
Future<bool> sentryReportMessage(String message, {Map<String, String>? context}) async { Future<bool> sentryReportMessage(
String message, {
Map<String, String>? context,
}) async {
if (SENTRY_DSN_KEY.isEmpty) {
return false;
}
final server_info = getServerInfo(); final server_info = getServerInfo();
final app_info = await getAppInfo(); final app_info = await getAppInfo();
@ -98,23 +103,22 @@ Future<bool> sentryReportMessage(String message, {Map<String, String>? context})
// We don't care about the server address, only the path and query parameters! // We don't care about the server address, only the path and query parameters!
// Overwrite the provided URL // Overwrite the provided URL
context["url"] = uri.path + "?" + uri.query; context["url"] = uri.path + "?" + uri.query;
} catch (error) { } catch (error) {
// Ignore if any errors are thrown here // Ignore if any errors are thrown here
} }
} }
} }
print("Sending user message to Sentry: ${message}, ${context}"); print("Sending user message to Sentry: ${message}, ${context}");
if (isInDebugMode()) { if (isInDebugMode()) {
print("----- In dev mode. Not sending message to Sentry.io -----"); print("----- In dev mode. Not sending message to Sentry.io -----");
return true; return true;
} }
final upload = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; final upload =
await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true)
as bool;
if (!upload) { if (!upload) {
print("----- Error reporting disabled -----"); print("----- Error reporting disabled -----");
@ -122,13 +126,16 @@ Future<bool> sentryReportMessage(String message, {Map<String, String>? context})
} }
Sentry.configureScope((scope) { Sentry.configureScope((scope) {
scope.setExtra("server", server_info); scope.setContexts("server", server_info);
scope.setExtra("app", app_info); scope.setContexts("app", app_info);
scope.setExtra("device", device_info); scope.setContexts("device", device_info);
if (context != null) { if (context != null) {
scope.setExtra("context", context); scope.setContexts("context", context);
} }
// Catch stacktrace data if possible
scope.setContexts("stacktrace", StackTrace.current.toString());
}); });
try { try {
@ -141,8 +148,19 @@ Future<bool> sentryReportMessage(String message, {Map<String, String>? context})
} }
} }
/*
Future<void> sentryReportError(dynamic error, dynamic stackTrace) async { * Report an error message to sentry.io
*/
Future<void> sentryReportError(
String source,
dynamic error,
StackTrace? stackTrace, {
Map<String, String> context = const {},
}) async {
if (sentryIgnoreError(error)) {
// No action on this error
return;
}
print("----- Sentry Intercepted error: $error -----"); print("----- Sentry Intercepted error: $error -----");
print(stackTrace); print(stackTrace);
@ -151,32 +169,85 @@ Future<void> sentryReportError(dynamic error, dynamic stackTrace) async {
// check if you are running in dev mode using an assertion and omit sending // check if you are running in dev mode using an assertion and omit sending
// the report. // the report.
if (isInDebugMode()) { if (isInDebugMode()) {
print("----- In dev mode. Not sending report to Sentry.io -----"); print("----- In dev mode. Not sending report to Sentry.io -----");
return; return;
} }
final upload = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; if (SENTRY_DSN_KEY.isEmpty) {
return;
}
final upload =
await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true)
as bool;
if (!upload) { if (!upload) {
print("----- Error reporting disabled -----"); print("----- Error reporting disabled -----");
return; return;
} }
// Some errors are outside our control, and we do not want to "pollute" the uploaded data
if (source == "FlutterError.onError") {
String errorString = error.toString();
// Missing media file
if (errorString.contains("HttpException") &&
errorString.contains("404") &&
errorString.contains("/media/")) {
return;
}
// Local file system exception
if (errorString.contains("FileSystemException")) {
return;
}
}
final server_info = getServerInfo(); final server_info = getServerInfo();
final app_info = await getAppInfo(); final app_info = await getAppInfo();
final device_info = await getDeviceInfo(); final device_info = await getDeviceInfo();
// Ensure we pass the 'source' of the error
context["source"] = source;
if (hasContext()) {
final ctx = OneContext().context;
if (ctx != null) {
context["widget"] = ctx.widget.toString();
context["widgetType"] = ctx.widget.runtimeType.toString();
}
}
Sentry.configureScope((scope) { Sentry.configureScope((scope) {
scope.setExtra("server", server_info); scope.setContexts("server", server_info);
scope.setExtra("app", app_info); scope.setContexts("app", app_info);
scope.setExtra("device", device_info); scope.setContexts("device", device_info);
scope.setContexts("context", context);
}); });
Sentry.captureException(error, stackTrace: stackTrace).catchError((error) { Sentry.captureException(error, stackTrace: stackTrace)
print("Error uploading information to Sentry.io:"); .catchError((error) {
print(error); print("Error uploading information to Sentry.io:");
}).then((response) { print(error);
print("Uploaded information to Sentry.io : ${response.toString()}"); return SentryId.empty();
}); })
.then((response) {
print("Uploaded information to Sentry.io : ${response.toString()}");
});
}
/*
* Test if a certain error should be ignored by Sentry
*/
bool sentryIgnoreError(dynamic error) {
// Ignore 404 errors for media files
if (error is HttpException) {
if (error.uri.toString().contains("/media/") &&
error.message.contains("404")) {
return true;
}
}
return false;
} }

View file

@ -0,0 +1,145 @@
/*
* Code for querying the server for various status code data,
* so that we do not have to duplicate those codes in the app.
*
* Ref: https://github.com/inventree/InvenTree/blob/master/InvenTree/InvenTree/status_codes.py
*/
import "package:flutter/material.dart";
import "package:inventree/api.dart";
import "package:inventree/app_colors.dart";
import "package:inventree/helpers.dart";
/*
* Base class definition for a "status code" definition.
*/
class InvenTreeStatusCode {
InvenTreeStatusCode(this.URL);
final String URL;
// Internal status code data loaded from server
Map<String, dynamic> data = {};
/*
* Construct a list of "choices" suitable for a form
*/
List<dynamic> get choices {
List<dynamic> _choices = [];
for (String key in data.keys) {
dynamic _entry = data[key];
if (_entry is Map<String, dynamic>) {
_choices.add({"value": _entry["key"], "display_name": _entry["label"]});
}
}
return _choices;
}
// Load status code information from the server
Future<void> load({bool forceReload = false}) async {
// Return internally cached data
if (data.isNotEmpty && !forceReload) {
return;
}
// The server must support this feature!
if (!InvenTreeAPI().supportsStatusLabelEndpoints) {
return;
}
debug("Loading status codes from ${URL}");
APIResponse response = await InvenTreeAPI().get(URL);
if (response.statusCode == 200) {
Map<String, dynamic> results = response.data as Map<String, dynamic>;
if (results.containsKey("values")) {
data = results["values"] as Map<String, dynamic>;
}
}
}
// Return the entry associated with the provided integer status
Map<String, dynamic> entry(int status) {
for (String key in data.keys) {
dynamic _entry = data[key];
if (_entry is Map<String, dynamic>) {
dynamic _status = _entry["key"];
if (_status is int) {
if (status == _status) {
return _entry;
}
}
}
}
// No match - return an empty map
return {};
}
// Return the 'label' associated with a given status code
String label(int status) {
Map<String, dynamic> _entry = entry(status);
String _label = (_entry["label"] ?? "") as String;
if (_label.isEmpty) {
// If no match found, return the status code
debug("No match for status code ${status} at '${URL}'");
return status.toString();
} else {
return _label;
}
}
// Return the 'name' (untranslated) associated with a given status code
String name(int status) {
Map<String, dynamic> _entry = entry(status);
String _name = (_entry["name"] ?? "") as String;
if (_name.isEmpty) {
debug("No match for status code ${status} at '${URL}'");
}
return _name;
}
// Test if the name associated with the given code is in the provided list
bool isNameIn(int code, List<String> names) {
return names.contains(name(code));
}
// Return the 'color' associated with a given status code
Color color(int status) {
Map<String, dynamic> _entry = entry(status);
String color_name = (_entry["color"] ?? "") as String;
switch (color_name.toLowerCase()) {
case "success":
return COLOR_SUCCESS;
case "primary":
return COLOR_PROGRESS;
case "secondary":
return Colors.grey;
case "dark":
return Colors.black;
case "danger":
return COLOR_DANGER;
case "warning":
return COLOR_WARNING;
case "info":
return Colors.lightBlue;
default:
return Colors.black;
}
}
}

View file

@ -1,182 +1,183 @@
import "dart:async"; import "dart:async";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:intl/intl.dart"; import "package:inventree/api.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/api.dart"; import "package:inventree/inventree/part.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/widget/stock/location_display.dart";
import "package:inventree/widget/stock/stock_detail.dart";
/*
* Class representing a test result for a single stock item
*/
class InvenTreeStockItemTestResult extends InvenTreeModel { class InvenTreeStockItemTestResult extends InvenTreeModel {
InvenTreeStockItemTestResult() : super(); InvenTreeStockItemTestResult() : super();
InvenTreeStockItemTestResult.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeStockItemTestResult.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
String get URL => "stock/test/"; String get URL => "stock/test/";
@override @override
Map<String, dynamic> formFields() { List<String> get rolesRequired => ["stock"];
return {
"stock_item": { @override
"hidden": true Map<String, String> defaultFilters() {
}, return {"user_detail": "true", "template_detail": "true"};
}
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"stock_item": {"hidden": true},
"test": {}, "test": {},
"template": {
"filters": {"enabled": "true"},
},
"result": {}, "result": {},
"value": {}, "value": {},
"notes": {}, "notes": {},
"attachment": {}, "attachment": {},
}; };
if (InvenTreeAPI().supportsModernTestResults) {
fields.remove("test");
} else {
fields.remove("template");
}
return fields;
} }
String get key => (jsondata["key"] ?? "") as String; String get key => getString("key");
String get testName => (jsondata["test"] ?? "") as String; int get templateId => getInt("template");
bool get result => (jsondata["result"] ?? false) as bool; String get testName => getString("test");
String get value => (jsondata["value"] ?? "") as String; bool get result => getBool("result");
String get attachment => (jsondata["attachment"] ?? "") as String; String get value => getString("value");
String get date => (jsondata["date"] ?? "") as String; String get attachment => getString("attachment");
String get username => getString("username", subKey: "user_detail");
String get date => getString("date");
@override @override
InvenTreeStockItemTestResult createFromJson(Map<String, dynamic> json) { InvenTreeStockItemTestResult createFromJson(Map<String, dynamic> json) {
var result = InvenTreeStockItemTestResult.fromJson(json); var result = InvenTreeStockItemTestResult.fromJson(json);
return result; return result;
} }
} }
class InvenTreeStockItemHistory extends InvenTreeModel { class InvenTreeStockItemHistory extends InvenTreeModel {
InvenTreeStockItemHistory() : super(); InvenTreeStockItemHistory() : super();
InvenTreeStockItemHistory.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeStockItemHistory.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
return InvenTreeStockItemHistory.fromJson(json); InvenTreeStockItemHistory.fromJson(json);
}
@override @override
String get URL => "stock/track/"; String get URL => "stock/track/";
@override @override
Map<String, String> defaultListFilters() { Map<String, String> defaultFilters() {
// By default, order by decreasing date // By default, order by decreasing date
return { return {"ordering": "-date", "user_detail": "true"};
"ordering": "-date",
};
} }
DateTime? get date { DateTime? get date => getDate("date");
if (jsondata.containsKey("date")) {
return DateTime.tryParse((jsondata["date"] ?? "") as String);
} else {
return null;
}
}
String get dateString { String get dateString => getDateString("date");
var d = date;
if (d == null) { String get label => getString("label");
return "";
}
return DateFormat("yyyy-MM-dd").format(d); // Return the "deltas" associated with this historical object
} Map<String, dynamic> get deltas => getMap("deltas");
String get label => (jsondata["label"] ?? "") as String;
// Return the quantity string for this historical object
String get quantityString { String get quantityString {
Map<String, dynamic> deltas = (jsondata["deltas"] ?? {}) as Map<String, dynamic>; var _deltas = deltas;
// Serial number takes priority here if (_deltas.containsKey("quantity")) {
if (deltas.containsKey("serial")) { double q = double.tryParse(_deltas["quantity"].toString()) ?? 0;
var serial = (deltas["serial"] ?? "").toString();
return "# ${serial}";
} else if (deltas.containsKey("quantity")) {
double q = (deltas["quantity"] ?? 0) as double;
return simpleNumberString(q); return simpleNumberString(q);
} else { } else {
return ""; return "";
} }
} }
int? get user => getValue("user") as int?;
String get userString {
if (user != null) {
return getString("username", subKey: "user_detail");
} else {
return "";
}
}
} }
/*
* Class representing a StockItem database instance
*/
class InvenTreeStockItem extends InvenTreeModel { class InvenTreeStockItem extends InvenTreeModel {
InvenTreeStockItem() : super(); InvenTreeStockItem() : super();
InvenTreeStockItem.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeStockItem.fromJson(Map<String, dynamic> json) : super.fromJson(json);
// Stock status codes
static const int OK = 10;
static const int ATTENTION = 50;
static const int DAMAGED = 55;
static const int DESTROYED = 60;
static const int REJECTED = 65;
static const int LOST = 70;
static const int RETURNED = 85;
String statusLabel(BuildContext context) {
// TODO: Delete me - The translated status values are provided by the API!
switch (status) {
case OK:
return L10().ok;
case ATTENTION:
return L10().attention;
case DAMAGED:
return L10().damaged;
case DESTROYED:
return L10().destroyed;
case REJECTED:
return L10().rejected;
case LOST:
return L10().lost;
case RETURNED:
return L10().returned;
default:
return status.toString();
}
}
// Return color associated with stock status
Color get statusColor {
switch (status) {
case OK:
return Colors.black;
case ATTENTION:
return Color(0xFFfdc82a);
case DAMAGED:
case DESTROYED:
case REJECTED:
return Color(0xFFe35a57);
case LOST:
default:
return Color(0xFFAAAAAA);
}
}
@override @override
String get URL => "stock/"; String get URL => "stock/";
// URLs for performing stock actions static const String MODEL_TYPE = "stockitem";
@override
List<String> get rolesRequired => ["stock"];
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => StockDetailWidget(this)),
);
}
// Return a set of fields to transfer this stock item via dialog
Map<String, dynamic> transferFields() {
Map<String, dynamic> fields = {
"pk": {"parent": "items", "nested": true, "hidden": true, "value": pk},
"quantity": {"parent": "items", "nested": true, "value": quantity},
"location": {"value": locationId},
"status": {"parent": "items", "nested": true, "value": status},
"packaging": {"parent": "items", "nested": true, "value": packaging},
"notes": {},
};
if (isSerialized()) {
// Prevent editing of 'quantity' field if the item is serialized
fields["quantity"]?["hidden"] = true;
}
// Old API does not support these fields
if (!api.supportsStockAdjustExtraFields) {
fields.remove("packaging");
fields.remove("status");
}
return fields;
}
// URLs for performing stock actions
static String transferStockUrl() => "stock/transfer/"; static String transferStockUrl() => "stock/transfer/";
static String countStockUrl() => "stock/count/"; static String countStockUrl() => "stock/count/";
@ -189,38 +190,32 @@ class InvenTreeStockItem extends InvenTreeModel {
String get WEB_URL => "stock/item/"; String get WEB_URL => "stock/item/";
@override @override
Map<String, dynamic> formFields() { Map<String, Map<String, dynamic>> formFields() {
return { Map<String, Map<String, dynamic>> fields = {
"part": {}, "part": {},
"location": {}, "location": {},
"quantity": {}, "quantity": {},
"serial": {},
"serial_numbers": {"label": L10().serialNumbers, "type": "string"},
"status": {}, "status": {},
"batch": {}, "batch": {},
"purchase_price": {},
"purchase_price_currency": {},
"packaging": {}, "packaging": {},
"link": {}, "link": {},
}; };
return fields;
} }
@override @override
Map<String, String> defaultGetFilters() { Map<String, String> defaultFilters() {
return {
"part_detail": "true",
"location_detail": "true",
"supplier_detail": "true",
"cascade": "false"
};
}
@override
Map<String, String> defaultListFilters() {
return { return {
"part_detail": "true", "part_detail": "true",
"location_detail": "true", "location_detail": "true",
"supplier_detail": "true", "supplier_detail": "true",
"supplier_part_detail": "true",
"cascade": "false", "cascade": "false",
"in_stock": "true",
}; };
} }
@ -229,20 +224,18 @@ class InvenTreeStockItem extends InvenTreeModel {
int get testTemplateCount => testTemplates.length; int get testTemplateCount => testTemplates.length;
// Get all the test templates associated with this StockItem // Get all the test templates associated with this StockItem
Future<void> getTestTemplates({bool showDialog=false}) async { Future<void> getTestTemplates({bool showDialog = false}) async {
await InvenTreePartTestTemplate().list( await InvenTreePartTestTemplate()
filters: { .list(filters: {"part": "${partId}", "enabled": "true"})
"part": "${partId}", .then((var templates) {
}, testTemplates.clear();
).then((var templates) {
testTemplates.clear();
for (var t in templates) { for (var t in templates) {
if (t is InvenTreePartTestTemplate) { if (t is InvenTreePartTestTemplate) {
testTemplates.add(t); testTemplates.add(t);
} }
} }
}); });
} }
List<InvenTreeStockItemTestResult> testResults = []; List<InvenTreeStockItemTestResult> testResults = [];
@ -250,101 +243,86 @@ class InvenTreeStockItem extends InvenTreeModel {
int get testResultCount => testResults.length; int get testResultCount => testResults.length;
Future<void> getTestResults() async { Future<void> getTestResults() async {
await InvenTreeStockItemTestResult()
.list(filters: {"stock_item": "${pk}", "user_detail": "true"})
.then((var results) {
testResults.clear();
await InvenTreeStockItemTestResult().list( for (var r in results) {
filters: { if (r is InvenTreeStockItemTestResult) {
"stock_item": "${pk}", testResults.add(r);
"user_detail": "true", }
}, }
).then((var results) { });
testResults.clear();
for (var r in results) {
if (r is InvenTreeStockItemTestResult) {
testResults.add(r);
}
}
});
} }
String get uid => (jsondata["uid"] ?? "") as String; bool get isInStock => getBool("in_stock", backup: true);
int get status => (jsondata["status"] ?? -1) as int; String get packaging => getString("packaging");
String get packaging => (jsondata["packaging"] ?? "") as String; String get batch => getString("batch");
String get batch => (jsondata["batch"] ?? "") as String; int get partId => getInt("part");
int get partId => (jsondata["part"] ?? -1) as int; double? get purchasePrice {
String pp = getString("purchase_price");
String get purchasePrice => (jsondata["purchase_price"] ?? "") as String;
if (pp.isEmpty) {
return null;
} else {
return double.tryParse(pp);
}
}
String get purchasePriceCurrency => getString("purchase_price_currency");
bool get hasPurchasePrice { bool get hasPurchasePrice {
double? pp = purchasePrice;
String pp = purchasePrice; return pp != null && pp > 0;
return pp.isNotEmpty && pp.trim() != "-";
} }
int get purchaseOrderId => (jsondata["purchase_order"] ?? -1) as int; int get purchaseOrderId => getInt("purchase_order");
int get trackingItemCount => (jsondata["tracking_items"] ?? 0) as int; int get trackingItemCount => getInt("tracking_items", backup: 0);
bool get isBuilding => (jsondata["is_building"] ?? false) as bool; bool get isBuilding => getBool("is_building");
int get salesOrderId => getInt("sales_order");
bool get hasSalesOrder => salesOrderId > 0;
int get customerId => getInt("customer");
bool get hasCustomer => customerId > 0;
bool get stale => getBool("stale");
bool get expired => getBool("expired");
DateTime? get expiryDate => getDate("expiry_date");
String get expiryDateString => getDateString("expiry_date");
// Date of last update // Date of last update
DateTime? get updatedDate { DateTime? get updatedDate => getDate("updated");
if (jsondata.containsKey("updated")) {
return DateTime.tryParse((jsondata["updated"] ?? "") as String);
} else {
return null;
}
}
String get updatedDateString { String get updatedDateString => getDateString("updated");
var _updated = updatedDate;
if (_updated == null) { DateTime? get stocktakeDate => getDate("stocktake_date");
return "";
}
final DateFormat _format = DateFormat("yyyy-MM-dd"); String get stocktakeDateString => getDateString("stocktake_date");
return _format.format(_updated);
}
DateTime? get stocktakeDate {
if (jsondata.containsKey("stocktake_date")) {
return DateTime.tryParse((jsondata["stocktake_date"] ?? "") as String);
} else {
return null;
}
}
String get stocktakeDateString {
var _stocktake = stocktakeDate;
if (_stocktake == null) {
return "";
}
final DateFormat _format = DateFormat("yyyy-MM-dd");
return _format.format(_stocktake);
}
String get partName { String get partName {
String nm = ""; String nm = "";
// Use the detailed part information as priority // Use the detailed part information as priority
if (jsondata.containsKey("part_detail")) { if (jsondata.containsKey("part_detail")) {
nm = (jsondata["part_detail"]["full_name"] ?? "") as String; nm = (jsondata["part_detail"]?["full_name"] ?? "") as String;
} }
// Backup if first value fails // Backup if first value fails
if (nm.isEmpty) { if (nm.isEmpty) {
nm = (jsondata["part__name"] ?? "") as String; nm = getString("part__name");
} }
return nm; return nm;
@ -355,11 +333,11 @@ class InvenTreeStockItem extends InvenTreeModel {
// Use the detailed part description as priority // Use the detailed part description as priority
if (jsondata.containsKey("part_detail")) { if (jsondata.containsKey("part_detail")) {
desc = (jsondata["part_detail"]["description"] ?? "") as String; desc = (jsondata["part_detail"]?["description"] ?? "") as String;
} }
if (desc.isEmpty) { if (desc.isEmpty) {
desc = (jsondata["part__description"] ?? "") as String; desc = getString("part__description");
} }
return desc; return desc;
@ -369,11 +347,11 @@ class InvenTreeStockItem extends InvenTreeModel {
String img = ""; String img = "";
if (jsondata.containsKey("part_detail")) { if (jsondata.containsKey("part_detail")) {
img = (jsondata["part_detail"]["thumbnail"] ?? "") as String; img = (jsondata["part_detail"]?["thumbnail"] ?? "") as String;
} }
if (img.isEmpty) { if (img.isEmpty) {
img = (jsondata["part__thumbnail"] ?? "") as String; img = getString("part__thumbnail");
} }
return img; return img;
@ -383,7 +361,6 @@ class InvenTreeStockItem extends InvenTreeModel {
* Return the Part thumbnail for this stock item. * Return the Part thumbnail for this stock item.
*/ */
String get partThumbnail { String get partThumbnail {
String thumb = ""; String thumb = "";
thumb = (jsondata["part_detail"]?["thumbnail"] ?? "") as String; thumb = (jsondata["part_detail"]?["thumbnail"] ?? "") as String;
@ -395,7 +372,7 @@ class InvenTreeStockItem extends InvenTreeModel {
// Try a different approach // Try a different approach
if (thumb.isEmpty) { if (thumb.isEmpty) {
thumb = (jsondata["part__thumbnail"] ?? "") as String; thumb = getString("part__thumbnail");
} }
// Still no thumbnail? Use the "no image" image // Still no thumbnail? Use the "no image" image
@ -404,49 +381,42 @@ class InvenTreeStockItem extends InvenTreeModel {
return thumb; return thumb;
} }
int get supplierPartId => (jsondata["supplier_part"] ?? -1) as int; int get supplierPartId => getInt("supplier_part");
String get supplierImage { String get supplierImage {
String thumb = ""; String thumb = "";
if (jsondata.containsKey("supplier_detail")) { if (jsondata.containsKey("supplier_part_detail")) {
thumb = (jsondata["supplier_detail"]["supplier_logo"] ?? "") as String; thumb =
(jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? "")
as String;
} else if (jsondata.containsKey("supplier_detail")) {
thumb = (jsondata["supplier_detail"]?["image"] ?? "") as String;
} }
return thumb; return thumb;
} }
String get supplierName { String get supplierName =>
String sname = ""; getString("supplier_name", subKey: "supplier_detail");
if (jsondata.containsKey("supplier_detail")) { String get units => getString("units", subKey: "part_detail");
sname = (jsondata["supplier_detail"]["supplier_name"] ?? "") as String;
String get supplierSKU => getString("SKU", subKey: "supplier_part_detail");
String get serialNumber => getString("serial");
double get quantity => getDouble("quantity");
String quantityString({bool includeUnits = true}) {
String q = "";
if (allocated > 0) {
q += simpleNumberString(available);
q += " / ";
} }
return sname; q += simpleNumberString(quantity);
}
String get units {
return (jsondata["part_detail"]?["units"] ?? "") as String;
}
String get supplierSKU {
String sku = "";
if (jsondata.containsKey("supplier_detail")) {
sku = (jsondata["supplier_detail"]["SKU"] ?? "") as String;
}
return sku;
}
String get serialNumber => (jsondata["serial"] ?? "") as String;
double get quantity => double.tryParse(jsondata["quantity"].toString()) ?? 0;
String quantityString({bool includeUnits = false}){
String q = simpleNumberString(quantity);
if (includeUnits && units.isNotEmpty) { if (includeUnits && units.isNotEmpty) {
q += " ${units}"; q += " ${units}";
@ -455,39 +425,45 @@ class InvenTreeStockItem extends InvenTreeModel {
return q; return q;
} }
int get locationId => (jsondata["location"] ?? -1) as int; double get allocated => getDouble("allocated");
double get available => quantity - allocated;
int get locationId => getInt("location");
bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1; bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1;
String serialOrQuantityDisplay() { String serialOrQuantityDisplay() {
if (isSerialized()) { if (isSerialized()) {
return "SN ${serialNumber}"; return "SN ${serialNumber}";
} else if (allocated > 0) {
return "${available} / ${quantity}";
} else {
return simpleNumberString(quantity);
} }
return simpleNumberString(quantity);
} }
String get locationName { String get locationName {
String loc = ""; if (locationId == -1 || !jsondata.containsKey("location_detail")) {
return "Unknown Location";
}
if (locationId == -1 || !jsondata.containsKey("location_detail")) return "Unknown Location"; String loc = getString("name", subKey: "location_detail");
loc = (jsondata["location_detail"]["name"] ?? "") as String;
// Old-style name // Old-style name
if (loc.isEmpty) { if (loc.isEmpty) {
loc = (jsondata["location__name"] ?? "") as String; loc = getString("location__name");
} }
return loc; return loc;
} }
String get locationPathString { String get locationPathString {
if (locationId == -1 || !jsondata.containsKey("location_detail")) {
return L10().locationNotSet;
}
if (locationId == -1 || !jsondata.containsKey("location_detail")) return L10().locationNotSet; String _loc = getString("pathstring", subKey: "location_detail");
String _loc = (jsondata["location_detail"]["pathstring"] ?? "") as String;
if (_loc.isNotEmpty) { if (_loc.isNotEmpty) {
return _loc; return _loc;
} else { } else {
@ -501,14 +477,19 @@ class InvenTreeStockItem extends InvenTreeModel {
if (serialNumber.isNotEmpty) { if (serialNumber.isNotEmpty) {
return "SN: $serialNumber"; return "SN: $serialNumber";
} else { } else {
return simpleNumberString(quantity); String q = simpleNumberString(quantity);
if (units.isNotEmpty) {
q += " ${units}";
}
return q;
} }
} }
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
return InvenTreeStockItem.fromJson(json); InvenTreeStockItem.fromJson(json);
}
/* /*
* Perform stocktake action: * Perform stocktake action:
@ -517,9 +498,12 @@ class InvenTreeStockItem extends InvenTreeModel {
* - Remove * - Remove
* - Count * - Count
*/ */
// TODO: Remove this function when we deprecate support for the old API Future<bool> adjustStock(
Future<bool> adjustStock(BuildContext context, String endpoint, double q, {String? notes, int? location}) async { String endpoint,
double q, {
String? notes,
int? location,
}) async {
// Serialized stock cannot be adjusted (unless it is a "transfer") // Serialized stock cannot be adjusted (unless it is a "transfer")
if (isSerialized() && location == null) { if (isSerialized() && location == null) {
return false; return false;
@ -532,72 +516,46 @@ class InvenTreeStockItem extends InvenTreeModel {
Map<String, dynamic> data = {}; Map<String, dynamic> data = {};
// Note: Format of adjustment API was updated in API v14 data = {
if (InvenTreeAPI().supportModernStockTransactions()) { "items": [
// Modern (> 14) API {"pk": "${pk}", "quantity": "${quantity}"},
data = { ],
"items": [ "notes": notes ?? "",
{ };
"pk": "${pk}",
"quantity": "${quantity}",
}
],
};
} else {
// Legacy (<= 14) API
data = {
"item": {
"pk": "${pk}",
"quantity": "${quantity}",
},
};
}
data["notes"] = notes ?? "";
if (location != null) { if (location != null) {
data["location"] = location; data["location"] = location;
} }
// Expected API return code depends on server API version var response = await api.post(endpoint, body: data);
final int expected_response = InvenTreeAPI().supportModernStockTransactions() ? 201 : 200;
var response = await api.post( return response.isValid() &&
endpoint, (response.statusCode == 200 || response.statusCode == 201);
body: data,
expectedStatusCode: expected_response,
);
return response.isValid();
} }
// TODO: Remove this function when we deprecate support for the old API Future<bool> countStock(double q, {String? notes}) async {
Future<bool> countStock(BuildContext context, double q, {String? notes}) async { final bool result = await adjustStock("/stock/count/", q, notes: notes);
final bool result = await adjustStock(context, "/stock/count/", q, notes: notes);
return result; return result;
} }
// TODO: Remove this function when we deprecate support for the old API Future<bool> addStock(double q, {String? notes}) async {
Future<bool> addStock(BuildContext context, double q, {String? notes}) async { final bool result = await adjustStock("/stock/add/", q, notes: notes);
final bool result = await adjustStock(context, "/stock/add/", q, notes: notes);
return result; return result;
} }
// TODO: Remove this function when we deprecate support for the old API Future<bool> removeStock(double q, {String? notes}) async {
Future<bool> removeStock(BuildContext context, double q, {String? notes}) async { final bool result = await adjustStock("/stock/remove/", q, notes: notes);
final bool result = await adjustStock(context, "/stock/remove/", q, notes: notes);
return result; return result;
} }
// TODO: Remove this function when we deprecate support for the old API Future<bool> transferStock(
Future<bool> transferStock(BuildContext context, int location, {double? quantity, String? notes}) async { int location, {
double? quantity,
String? notes,
}) async {
double q = this.quantity; double q = this.quantity;
if (quantity != null) { if (quantity != null) {
@ -605,7 +563,6 @@ class InvenTreeStockItem extends InvenTreeModel {
} }
final bool result = await adjustStock( final bool result = await adjustStock(
context,
"/stock/transfer/", "/stock/transfer/",
q, q,
notes: notes, notes: notes,
@ -616,29 +573,43 @@ class InvenTreeStockItem extends InvenTreeModel {
} }
} }
class InvenTreeStockLocation extends InvenTreeModel { class InvenTreeStockLocation extends InvenTreeModel {
InvenTreeStockLocation() : super(); InvenTreeStockLocation() : super();
InvenTreeStockLocation.fromJson(Map<String, dynamic> json) : super.fromJson(json); InvenTreeStockLocation.fromJson(Map<String, dynamic> json)
: super.fromJson(json);
@override @override
String get URL => "stock/location/"; String get URL => "stock/location/";
String get pathstring => (jsondata["pathstring"] ?? "") as String; static const String MODEL_TYPE = "stocklocation";
@override @override
Map<String, dynamic> formFields() { List<String> get rolesRequired => ["stock"];
return {
String get pathstring => getString("pathstring");
@override
Future<Object?> goToDetailPage(BuildContext context) async {
return Navigator.push(
context,
MaterialPageRoute(builder: (context) => LocationDisplayWidget(this)),
);
}
@override
Map<String, Map<String, dynamic>> formFields() {
Map<String, Map<String, dynamic>> fields = {
"name": {}, "name": {},
"description": {}, "description": {},
"parent": {}, "parent": {},
"structural": {},
}; };
return fields;
} }
String get parentpathstring { String get parentPathString {
// TODO - Drive the refactor tractor through this
List<String> psplit = pathstring.split("/"); List<String> psplit = pathstring.split("/");
if (psplit.isNotEmpty) { if (psplit.isNotEmpty) {
@ -657,10 +628,6 @@ class InvenTreeStockLocation extends InvenTreeModel {
int get itemcount => (jsondata["items"] ?? 0) as int; int get itemcount => (jsondata["items"] ?? 0) as int;
@override @override
InvenTreeModel createFromJson(Map<String, dynamic> json) { InvenTreeModel createFromJson(Map<String, dynamic> json) =>
InvenTreeStockLocation.fromJson(json);
var loc = InvenTreeStockLocation.fromJson(json); }
return loc;
}
}

View file

@ -1,12 +1,18 @@
import "package:flutter_gen/gen_l10n/app_localizations.dart"; import "package:inventree/l10n/collected/app_localizations.dart";
import "package:flutter_gen/gen_l10n/app_localizations_en.dart"; import "package:inventree/l10n/collected/app_localizations_en.dart";
import "package:one_context/one_context.dart"; import "package:one_context/one_context.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:inventree/helpers.dart";
// Shortcut function to reduce boilerplate! // Shortcut function to reduce boilerplate!
I18N L10() I18N L10() {
{ // Testing mode - ignore context
if (!hasContext()) {
return I18NEn();
}
BuildContext? _ctx = OneContext().context; BuildContext? _ctx = OneContext().context;
if (_ctx != null) { if (_ctx != null) {
@ -18,5 +24,5 @@ I18N L10()
} }
// Fallback for "null" context // Fallback for "null" context
return I18NEn(); return I18NEn();
} }

@ -1 +0,0 @@
Subproject commit a1683e462bb8c91d481cdacb169cb98d63e93acc

3
lib/l10n/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Do not track the collected translation files
collected/
supported_locales.dart

23
lib/l10n/README.md Normal file
View file

@ -0,0 +1,23 @@
## InvenTree Translation Files
This directory contains translation files for the InvenTree mobile app.
### File Structure
**Translation Source File** - app_en.arb
This file contains the source strings for translating. If you want to add a new translatable string to the app, is must be added to this file!
**Translated Files** - <lc>/app_<lb>arb
Each directory contains a single translation output file, generated by the [crowdin translation service](https://crowdin.com/project/inventree). *Do not edit these files*
**collected** - Collected files
Before building the app, the translation files are collected from the various directories into a single directory, so they can be accessed by the app.
### Translating
DO NOT EDIT THE TRANSLATION FILES DIRECTLY!
Translation files are crowd sourced using the [crowdin service](https://crowdin.com/project/inventree). Contributions are welcomed (and encouraged!)

1793
lib/l10n/app_en.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/ar_SA/app_ar_SA.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/bg_BG/app_bg_BG.arb Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,154 @@
"""
Collect translation files into a single directory,
where they can be accessed by the flutter i18n library.
Translations provided from crowdin are located in subdirectories,
but we need the .arb files to appear in this top level directory
to be accessed by the app.
So, simply copy them here!
"""
import os
import glob
from posixpath import dirname
import shutil
import re
def process_locale_file(filename, locale_name):
"""
Process a locale file after copying
- Ensure the 'locale' matches
"""
# TODO: Use JSON processing instead of manual
# Need to work out unicode issues for this to work
with open(filename, "r", encoding="utf-8") as input_file:
lines = input_file.readlines()
with open(filename, "w", encoding="utf-8") as output_file:
# Using JSON processing would be simpler here,
# but it does not preserve unicode data!
for line in lines:
if "@@locale" in line:
new_line = f' "@@locale": "{locale_name}"'
if "," in line:
new_line += ","
new_line += "\n"
line = new_line
output_file.write(line)
def copy_locale_file(path):
"""
Locate and copy the locale file from the provided directory
"""
here = os.path.abspath(os.path.dirname(__file__))
for f in os.listdir(path):
src = os.path.join(path, f)
dst = os.path.join(here, "collected", f)
if os.path.exists(src) and os.path.isfile(src) and f.endswith(".arb"):
shutil.copyfile(src, dst)
print(f"Copied file '{f}'")
locale = os.path.split(path)[-1]
process_locale_file(dst, locale)
# Create a "fallback" locale file, without a country code specifier, if it does not exist
r = re.search(r"app_(\w+)_(\w+).arb", f)
locale = r.groups()[0]
fallback = f"app_{locale}.arb"
fallback_file = os.path.join(here, "collected", fallback)
if not os.path.exists(fallback_file):
print(f"Creating fallback file:", fallback_file)
shutil.copyfile(dst, fallback_file)
process_locale_file(fallback_file, locale)
def generate_locale_list(locales):
"""
Generate a .dart file which contains all the supported locales,
for importing into the project
"""
with open("supported_locales.dart", "w") as output:
output.write(
"// This file is auto-generated by the 'collect_translations.py' script - do not edit it directly!\n\n"
)
output.write("// dart format off\n\n")
output.write('import "package:flutter/material.dart";\n\n')
output.write("const List<Locale> supported_locales = [\n")
locales = sorted(locales)
for locale in locales:
if locale.startswith("."):
continue
splt = locale.split("_")
if len(splt) == 2:
lc, cc = splt
else:
lc = locale
cc = ""
output.write(
f' Locale("{lc}", "{cc}"), // Translations available in app_{locale}.arb\n'
)
output.write("];\n")
output.write("")
if __name__ == "__main__":
here = os.path.abspath(os.path.dirname(__file__))
# Ensure the 'collected' output directory exists
output_dir = os.path.join(here, "collected")
os.makedirs(output_dir, exist_ok=True)
# Remove existing .arb files from output directory
arbs = glob.glob(os.path.join(output_dir, "*.arb"))
for arb in arbs:
os.remove(arb)
locales = ["en"]
for locale in os.listdir(here):
# Ignore the output directory
if locale == "collected":
continue
f = os.path.join(here, locale)
if os.path.exists(f) and os.path.isdir(locale):
copy_locale_file(f)
locales.append(locale)
# Ensure the translation source file ('app_en.arb') is copied also
# Note that this does not require any further processing
src = os.path.join(here, "app_en.arb")
dst = os.path.join(here, "collected", "app_en.arb")
shutil.copyfile(src, dst)
generate_locale_list(locales)
print(f"Updated translations for {len(locales)} locales.")

1215
lib/l10n/cs_CZ/app_cs_CZ.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/da_DK/app_da_DK.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/de_DE/app_de_DE.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/el_GR/app_el_GR.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/es_ES/app_es_ES.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/es_MX/app_es_MX.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/et_EE/app_et_EE.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/fa_IR/app_fa_IR.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/fi_FI/app_fi_FI.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/fr_FR/app_fr_FR.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/he_IL/app_he_IL.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/hi_IN/app_hi_IN.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/hu_HU/app_hu_HU.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/id_ID/app_id_ID.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/it_IT/app_it_IT.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/ja_JP/app_ja_JP.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/ko_KR/app_ko_KR.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/lt_LT/app_lt_LT.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/lv_LV/app_lv_LV.arb Normal file

File diff suppressed because it is too large Load diff

1215
lib/l10n/nl_NL/app_nl_NL.arb Normal file

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more