diff --git a/.vscode/settings.json b/.vscode/settings.json index ff3e6ce..041e944 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,46 +6,13 @@ "**/CVS": true, "**/.DS_Store": true, "**/Thumbs.db": true, - "venv": true, - ".vscode": true, - "__pycache__": true, - ".pytest_cache": true, - "narcotics_tracker/.pytest_cache": true, - "narcotics_tracker/__pycache__": true, - "tests/.pytest_cache": true, - "tests/unit/__pycache__": true, - "tests/unit/.pytest_cache": true, - "narcotics_tracker/container/__pycache__": true, - "narcotics_tracker/units/__pycache__": true, - "narcotics_tracker/medication/__pycache__": true, - "narcotics_tracker/converter/__pycache__": true, - "narcotics_tracker/containers/__pycache__": true, - "tests/unit/medication_tests/__pycache__": true, - "tests/unit/units/__pycache__": true, - "tests/__pycache__": true, - "tests/unit/medication_tests/.pytest_cache": true, - "narcotics_tracker/database_writer/__pycache__": true, - "tests/unit/database_writer_tests/__pycache__": true, - "tests/unit/units/.pytest_cache": true, - "tests/unit/database_tests/__pycache__": true, - "tests/unit/setup_tests/.pytest_cache": true, - "tests/unit/setup_tests/__pycache__": true, - "narcotics_tracker/setup/__pycache__": true, - "narcotics_tracker/database/__pycache__": true, - "tests/unit/units_tests/__pycache__": true, - "tests/unit/units_tests/.pytest_cache": true, - "tests/unit/database_tests/.pytest_cache": true, - "scripts/__pycache__": true, - "narcotics_tracker/date/__pycache__": true, - "narcotics_tracker/builders/__pycache__": true, - "narcotics_tracker/enums/__pycache__": true, - "narcotics_tracker/utils/__pycache__": true, - "scripts/cli/__pycache__": true + "**/__pycache__": true, + "**/.pytest_cache": true, + "**/.vscode": true, + "**/venv": true }, "hide-files.files": [ "venv", - ".vscode", - "__pycache__", ".pytest_cache", "narcotics_tracker/.pytest_cache", "narcotics_tracker/__pycache__", @@ -77,7 +44,21 @@ "narcotics_tracker/builders/__pycache__", "narcotics_tracker/enums/__pycache__", "narcotics_tracker/utils/__pycache__", - "scripts/cli/__pycache__" + "scripts/cli/__pycache__", + "persistence/__pycache__", + "tests/unit/builders/__pycache__", + "tests/unit/interfaces/__pycache__", + "tests/unit/persistence/__pycache__", + "narcotics_tracker/persistence/__pycache__", + "__pycache__", + ".vscode", + "narcotics_tracker/.vscode", + "narcotics_tracker/items/__pycache__", + "tests/unit/items/.pytest_cache", + "tests/unit/items/__pycache__", + "tests/unit/builders/.pytest_cache", + "tests/unit/scripts/__pycache__", + "tests/Integration/__pycache__" ], "vsicons.associations.folders": [ { @@ -122,6 +103,10 @@ "python.formatting.provider": "black", "cSpell.words": [ "Abenthy", + "adjustmentbuilder", + "apap", + "dataclass", + "dataitem", "Denna", "Elodin", "fent", @@ -129,10 +114,13 @@ "meds", "midaz", "MIDAZOLAM", + "reportingperiod", + "tablewriter", "unitconverter", "unixepoch", "Unobtainium", - "Unobtanium" + "Unobtanium", + "wlvac" ], "python.testing.cwd": "/Users/scottkostolni/Programming Projects/narcotics_tracker/", "[python]": { @@ -147,5 +135,12 @@ ], "githubPullRequests.ignoredPullRequestBranches": [ "master" - ] + ], + "python.terminal.activateEnvironment": false, + "explorer.compactFolders": false, + "explorer.decorations.badges": true, + "explorer.excludeGitIgnore": false, + "explorer.sortOrder": "filesFirst", + "vsicons.presets.hideExplorerArrows": true, + "explorer.fileNesting.enabled": false } \ No newline at end of file diff --git a/README.md b/README.md index e501649..273b706 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ Logo -

Narcotics Tracker

+

Narcotics Tracker

- The Narcotics Tracker project is a python program designed to assist controlled substance agents of EMS agencies with tracking of their narcotic inventory. + A utility for managing controlled substance medication inventory for EMS Agencies.
- Explore the docs » + Explore The Docs »

View Demo @@ -77,6 +77,13 @@ [![Product Name Screen Shot][product-screenshot]](https://example.com) +Management of controlled substance medications for EMS agencies requires +frequent updates and detailed reporting of medication inventory changes to +state and federal agencies. This goal of the **Narcotics Tracker** is to reduce +the manual workload required by controlled substance agents by providing an +intuitive system to record inventory changes and generate data required for +periodic reporting. +

(back to top)

### Built With @@ -136,11 +143,47 @@ _For more examples, please refer to the [Documentation](https://example.com)_ ## Roadmap -- [ ] Design - - [ ] Sketch out design document covering basic overview and features to - start. - -See the +- [x] Design + - [x] Outline program requirements. + - [x] Mock up database design. + - [x] Mock up class diagrams. +- [x] Version 0.1.0 - Alpha - Released On August 24th, 2022 + - [x] Medication creation + - [x] Container, Status and Unit creation. + - [x] Persistent data storage with an SQLite3 database. + - [x] Test suite development. +- [x] Version 0.2.0 - Alpha - Released On September 14th, 2022 + - [x] Event and Reporting Period creation. + - [x] Adjustment creation and the inventory management enabled. + - [x] Date Management enabled through SQLite3. + - [x] Database Context Manager added. + - [x] Continued expansion of the Test Suite. +- [ ] Version 0.2.5 - Alpha - In progress! + - [ ] Design architecture rework. + - [ ] Design pattern implementation to reduce coupling and increase + flexibility and ease of extension. + - [ ] Documentation update. + - [ ] Continued Test Suite expansion. +- [ ] Version 0.3.0 - Alpha + - [ ] Basic Report Generation +- [ ] Version 0.0.0 - Beta + - [ ] Command Line User Interface +- [ ] Version 0.1.0 - Beta + - [ ] Order Tracking +- [ ] Version 0.2.0 - Beta + - [ ] Order Tracking +- [ ] Version 0.3.0 - Beta + - [ ] Destruction Tracking +- [ ] Version 0.4.0 - Beta + - [ ] Controlled Substance Agent Management +- [ ] Version 0.5.0 - Beta + - [ ] Command Line Tools Interface Update +- [ ] Version 0.6.0 - Beta + - [ ] Console User Interface +- [ ] Version 0.7.0 - Beta + - [ ] Graphical User Interface + +See [open issues](https://github.com/ScottSucksAtProgramming/narcotics_tracker/issues) for a full list of proposed features (and known issues). diff --git a/assets/diagrams/builders/class_diagrams_builders_mermaid.md b/assets/diagrams/builders/class_diagrams_builders_mermaid.md new file mode 100644 index 0000000..3617583 --- /dev/null +++ b/assets/diagrams/builders/class_diagrams_builders_mermaid.md @@ -0,0 +1,106 @@ +# Narcotics Tracker Builder Diagrams + +## Builder Interface + +```mermaid +class BuilderInterface { + <> + +build() + -_reset() +} +``` + +## DataItem Builder +```mermaid +class DataItemBuilder { + +__init__() + -_reset() Exception + +build() Exception + +set_table() + +set_id() + +set_created_date() + +set_modified_date() + +set_modified_by +} +``` + +## Adjustment Builder +```mermaid +class AdjustmentBuilder { + -DataItem _dataitem + -_reset() + +build() Adjustment + +set_adjustment_date() + +set_event_code() + +set_medication_code() + +set_adjustment_amount() + +set_reference_id() + +set_reporting_period_id() +} +``` + +## Event Builder +```mermaid +class EventBuilder { + -DataItem _dataitem + -_reset() + +build() Event + +set_event_code() + +set_event_name() + +set_description() + +set_modifier() +} +``` + +## Medication Builder +```mermaid +class MedicationBuilder { + -DataItem _dataitem + -_reset() + +build() Medication + +set_medication_code() + +set_medication_name() + +set_fill_amoun() + +set_medication_amount() + +set_preferred_unit()) + +set_concentration() + +set_status() +} +``` + + +## Reporting Period Builder +```mermaid +class ReportingPeriodBuilder { + -DataItem _dataitem + -_reset() + +build() ReportingPeriod + +set_start_date() + +set_end_date() + +set_status() +} +``` + +## Status Builder +```mermaid +class StatusBuilder { + -DataItem _dataitem + -_reset() + +build() Status + +set_status_code() + +set_status_name() + +set_description() +} +``` + +## Unit Builder +```mermaid +class UnitBuilder { + -DataItem _dataitem + -_reset() + +build() Unit + +set_unit_code() + +set_unit_name() + +set_decimals() +} +``` \ No newline at end of file diff --git a/assets/diagrams/builders/relationship_diagram_builders_package_mermaid.md b/assets/diagrams/builders/relationship_diagram_builders_package_mermaid.md new file mode 100644 index 0000000..218e68d --- /dev/null +++ b/assets/diagrams/builders/relationship_diagram_builders_package_mermaid.md @@ -0,0 +1,191 @@ + + +# Builders Package: Relationship Class Diagram +```mermaid +classDiagram + +BuilderInterface <|.. DataItemBuilder : Implements + + class BuilderInterface { + <> + +build() + -_reset() + } + + class DataItemBuilder { + +__init__() + -_reset() Exception + +build() Exception + +set_table() + +set_id() + +set_created_date() + +set_modified_date() + +set_modified_by + } + +DataItemBuilder <|-- AdjustmentBuilder : Inherits + + class AdjustmentBuilder { + -DataItem _dataitem + -_reset() + +build() Adjustment + +set_adjustment_date() + +set_event_code() + +set_medication_code() + +set_adjustment_amount() + +set_reference_id() + +set_reporting_period_id() + } + +DataItemBuilder <|-- EventBuilder : Inherits + + class EventBuilder { + -DataItem _dataitem + -_reset() + +build() Event + +set_event_code() + +set_event_name() + +set_description() + +set_modifier() + } + +DataItemBuilder <|-- MedicationBuilder : Inherits + + class MedicationBuilder { + -DataItem _dataitem + -_reset() + +build() Medication + +set_medication_code() + +set_medication_name() + +set_fill_amoun() + +set_medication_amount() + +set_preferred_unit()) + +set_concentration() + +set_status() + } + +DataItemBuilder <|-- ReportingPeriodBuilder : Inherits + + class ReportingPeriodBuilder { + -DataItem _dataitem + -_reset() + +build() ReportingPeriod + +set_start_date() + +set_end_date() + +set_status() + } + +DataItemBuilder <|-- StatusBuilder : Inherits + + class StatusBuilder { + -DataItem _dataitem + -_reset() + +build() Status + +set_status_code() + +set_status_name() + +set_description() + } + +DataItemBuilder <|-- UnitBuilder : Inherits + + class UnitBuilder { + -DataItem _dataitem + -_reset() + +build() Unit + +set_unit_code() + +set_unit_name() + +set_decimals() + } + +AdjustmentBuilder ..|> Adjustment : << instantiates >> + + class Adjustment { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +int adjustment_date + +str event_code + +str medication_code + +float amount + +str reference_id + +int reporting_period_id + -__str__() str + } + +EventBuilder ..|> Event : << instantiates >> + + class Event { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str event_code + +str even_name + +str description + +int modifier + -__str__() str + } + +MedicationBuilder ..|> Medication : << instantiates >> + + class Medication { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str medication_code + +str medication_name + +float fill_amount + +float medication_amount + +str preferred_unit + +float concentration + +str status + -__str__() str + } + +ReportingPeriodBuilder ..|> ReportingPeriod : << instantiates >> + + class ReportingPeriod { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +int start_date + +int end_date + +str status + -__str__() str + } + +StatusBuilder ..|> Status : << instantiates >> + + class Status { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str status_code + +str status_name + +str description + -__str__() str + } + +UnitBuilder ..|> Unit : << instantiates >> + + class Unit { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str unit_code + +str unit_name + +int decimals + -__str__() str + } +``` \ No newline at end of file diff --git a/assets/diagrams/commands/relationship_diagram_dataitem_commands.svg b/assets/diagrams/commands/relationship_diagram_dataitem_commands.svg new file mode 100644 index 0000000..7ad5b6e --- /dev/null +++ b/assets/diagrams/commands/relationship_diagram_dataitem_commands.svg @@ -0,0 +1 @@ +
Inherits From
Inherits From
Inherits From
Inherits From
Inherits From
Inherits From
Implements
Uses
Acts On
«Abstract Base Class»
DatabaseItems
+ String Table_Name
+ Dict Column_Info
+ Integer ID
+ Integer Created_Date
+ Integer Modified_Date
+ String Modified_By
+Save(CommandInterface)
+Load(CommandInterface)
+Update(CommandInterface)
«DataClass / Invoker»
Adjustments
+ Unixepoch AdjustmentDate
+ String EventCode*
+ String MedicationCode*
+ Integer AdjustmentAmount
+ String ReferenceID
«DataClass / Invoker»
Events
+ String Code
+ String Name
+ String Description
+ Integer Modifier
«DataClass / Invoker»
Medications
+ String MedicationCode
+ String MedicationName
+ Float FillAmount
+ Float MedicationAmount
+ Unit PreferredUnit*
+ Float Concentration
«DataClass / Invoker»
ReportingPeriods
+ Unixepoch StartDate
+ Unixepoch EndDate
«DataClass / Invoker»
Statuses
+ String StatusCode
+ String StatusName
+ String Description
«DataClass / Invoker»
Units
+ String UnitCode
+ String UnitName
+ Integer Decimals
«Receiver»
Database
+ Table Events
+ Table Inventory
+ Table Medications
+ Table ReportingPeriods
+ Table Statuses
+ Table Units
«Protocol»
CommandInterface
+execute()
Commands
+ CommandInterface
\ No newline at end of file diff --git a/assets/diagrams/items/class_diagrams_dataitems_mermaid.md b/assets/diagrams/items/class_diagrams_dataitems_mermaid.md new file mode 100644 index 0000000..3f4d82d --- /dev/null +++ b/assets/diagrams/items/class_diagrams_dataitems_mermaid.md @@ -0,0 +1,111 @@ +# Mermaid Data Item Class Diagrams +{{TOC}} + +## DataItem - Inteface +```mermaid +class DataItem { + <> + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +__str__() str +} +``` + +## Adjustment +```mermaid +class Adjustment { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +int adjustment_date + +str event_code + +str medication_code + +float amount + +str reference_id + +int reporting_period_id + -__str__() str +} +``` +## Event +```mermaid +class Event { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str event_code + +str even_name + +str description + +int modifier + -__str__() str +} +``` +## Medication +```mermaid +class Medication { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str medication_code + +str medication_name + +float fill_amount + +float medication_amount + +str preferred_unit + +float concentration + +str status + -__str__() str +} +``` + +## ReportingPeriod +```mermaid +class ReportingPeriod { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +int start_date + +int end_date + +str status + -__str__() str +} +``` + +## Status +```mermaid +class Status { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str status_code + +str status_name + +str description + -__str__() str +} +``` + +## Unit +```mermaid +class Unit { + +str table + +int id + +int created_date + +int modified_date + +str modified_by + +str unit_code + +str unit_name + +int decimals + -__str__() str +} +``` diff --git a/docs/Inventory Module Release.md b/docs/Inventory Module Release.md deleted file mode 100644 index 0fe637b..0000000 --- a/docs/Inventory Module Release.md +++ /dev/null @@ -1,20 +0,0 @@ -# Inventory Module Release - -## What do I need to do to release the Inventory Module? -- [ ] Write Release Notes -- [ ] Update README -- [ ] Update Documentation -- [ ] Organize Code into set style. - - - -## Imports -- [ ] Remove unnecessary Imports -- [ ] Imports should be organized as follows and separated by blank lines: - - [ ] Third Party Libraries - - [ ] Built In Imports - - [ ] Project / Local Imports - - [ ] Imports should be sorted as follows: - - [ ] Direct Import Statements (i.e `import sqlite`) - - [ ] From Import Statements (i.e `from module import function`) - - [ ] \ No newline at end of file diff --git a/docs/design/00_design_overview.md b/docs/design/00_design_overview.md deleted file mode 100644 index 9c03cb3..0000000 --- a/docs/design/00_design_overview.md +++ /dev/null @@ -1,77 +0,0 @@ -# Design Doc Part 0 - Overview / Table of Contents - -|Section | Version | Created | Updated | -|:-------| :------ | :--------- | :--------- | -| 0 | 0.1.0 | 08/01/2022 | 08/24/2022 | - -{{TOC}} - -## Summary - -Welcome to the **Narcotics Tracker**! This software is designed to assist controlled substance agents working at EMS agencies with inventory tracking and periodic reporting of controlled substance activities to their governing agencies. - -### Motivation -I am a controlled substance agent working in New York State. The tracking of controlled substances is complicated and requires multiple forms and processes. I have wished for a single place where all of that information can be stored and easily accessed. This software is intended to fill that need. - -### Goals - -The **Narcotics Tracker** will provide a single place to enter all changes to an agencies controlled substance inventory. Each change in the inventory can be entered peridoically where the data can be queried to return information requried for reporting and compliance with tracking specifications. - -##### Project Specifications - 1. Inventory tracking of all controlled substances used by an EMS agency. - 2. Ability to create agency specific medications. - 3. Tracking of medication lots as they as disbursed to sub-stocks. - 4. Tracking of medication orders. - 5. Tracking of medication destruction. - 6. Tracking of medication administration. - 7. Tracking of medication waste. - 8. Built in reports which can be generated at the users request which fulfill the New York State and DEA requirements. - 9. A simple but powerful console user interface. - 10. A graphical user interface. - -I am a self-taught programmer looking to improve my knowledge and experience in soft-ware design by building projects which have practical applications. - -##### Personal Learning Goals - 1. Increase my knowledge and experience with Python. - 2. Learn about Object Oriented Programming. - 3. Practice and gain experience with Test Driven Development - 4. Gain knowledge on the storage, and manipulation, of data and the use of databases. - 5. Potentially branch out into GUI development with Python or different languages as necessary. - 6. To put out a product which I may be able to use to help generate extra income though licensing and service contracts. - -## Development Roadmap / Progress - -I'm not entirely sure where the best place to begin is. I do not have a enough -experience to know how to design this kind of software. I'll be using a lot of -trial and error. Here is my imagined development Path. - -- [x] Medication Creation and Management - - [x] Builder Pattern -- [x] Communication with a Database -- [ ] Inventory Management -- [ ] Medication Use -- [ ] Medication Waste -- [ ] Medication Destruction -- [ ] Medication Orders -- [ ] Lot Management -- [ ] Agent Management -- [ ] Provider Management -- [ ] Report Generation -- [ ] Console User Interface -- [ ] Graphical User Interface - -## Table Of Contents - -* [Medications](01_medications.md) -* [Database](02.database.md) -* [Inventory](03_inventory.md) -* Medication Use -* Medication Waste -* Medication Destruction -* [Medication Orders](07_orders.md) -* Lot Management -* Agent Management -* Provider Management -* Report Generation -* Console UI -* Graphical UI diff --git a/docs/design/Inventory Part 1- Designing the Inventory Feature.md b/docs/design/Inventory Part 1- Designing the Inventory Feature.md deleted file mode 100644 index 27e002e..0000000 --- a/docs/design/Inventory Part 1- Designing the Inventory Feature.md +++ /dev/null @@ -1,102 +0,0 @@ -# Inventory Part 1- Designing the Inventory Feature - -{{TOC}} - -In the previous series I designed and implemented the [representation of medications](LINK) that will be used within the **Narcotics Tracker**. With medications defined we can move on to what this project is all about: **Inventory Tracking**. - -My overall goal for this project was to created a simple interface where all controlled substance inventory changes can be locked and tracked. Lets take a look at what that looks like in this project. - -## Events -Each row in the Inventory Table is going to represent an event which changes the stock (or amount) of a medication. - -### Inventory Event Attributes -There are a handfull of data points which are important for inventory tracking. - -**`event_id`** -: Unique identifier for a specific event. - -**`event_date`** -: The date the event occured on. - -**`event_code`** -: The type of event which occured. - -**`medication_code`** -: The code of the specific medication which was effected by the event. - -: Pulled from the **Medication Table**. - -**`quanitity_in_mcg`** -: The amount of the medication which was changed. - -: Always handled in micrograms within the **Narcotics Tracker**. - -**`reporting_period_id`** -: The reporting period in which the event occured during. - -: Pulled from the **Reporting Periods Table**. - -**`reference_id`** -: A unique identifier pointing to a entry in a different table which provides more information about this event. - -: Example: A medication which was used during patient care would use the ePCR Number as its `reference_id`. - -: Example: A medication which was received from an order would use the Purchase Order Number as its `reference_id`. - -Additionally **`created_date`**, **`modified_date`**, and **`modified_by`** data points will be used. - -### Adding Stock to the Inventory -Using the [Medication Feature](LINK) we can define the different medications that are used at our agency. The three medications I used are Fentanyl, Morphine and Midazolam. - -Controlled substance medications cannot be exchanged, or traded between agencies or healthcare facilities. The transfer of these medications require trackable orders and certain medications require the use of 222 Forms. - -**222 Form** -: A form issued to approved medical providers which are used to purchase and transfer controlled substances. - -The primary way agencies will obtain new controlled substances is by *ordering* them. Agencies which may use this software likely have a stock of controlled substances from previous orders. To accomodate this medications will also be able to be *imported* into the inventory. - -### Removing Stock from the Inventory -There are multiple ways in which an amount of a medication can be removed from the inventory. - -The most frequent is *patient use*; the medication is given to a patient. Medications are also removed from the inventory when partial vials are *wasted*. Expired vials which are unopened have to be sent out for *destruction*. - -Medications can all be *lost*, though this is a rare occurence and must be investigated. - -### Intended Use Case -I imagine that users of the **Narcotics Tracker** will sit down periodically (I do it about once per week,) to update the inventory and log all changes. - -Logging of events in the inventory table is something users may do manually, though I have the ability to print up a report from my electronic patient care reporting software. I may wish to be able to have those reports added directly using the comma separated values format. - -## Requirements -There are a bunch of requirements outlined above which can be broken down a few peces. - -### Event Types - 1. The creation of an Events Type Table. - 2. The ability to create, update, read and delete event types from the table. - -### Reporting Periods - 1. The creation of a reporting period table. - 2. The creation of a Reporting Period class to handle reporting periods. - 3. The ability for reporting periods to be created, read, updated, and deleted from the table. - -### EventType Class - 1. The creation of an EventTypes Class to handle the actual events as objects. - 2. The ability for users to create new event types, specify their details, and save them into the Event Types Table. - 3. The ability for users to read and update events already saved in the table. - 4. The ability for users to delete event types from the Table (this may be removed in the future). - - -### Inventory Table - 1. Creation of the Inventory Table. - 2. Creation of an Adjustment Class to handle actual inventory changes. - 3. Ability for users to create adjustments, specify their details and save them into the inventory table. - 4. Ability for events to multiply the operator against the amount that was changed. - 5. Ability for the amount to be converted from the preferred unit to the standard unit (mcg). - 5. A mechanism where new events are assigned the appropriate `reporting_period_id', `created_date`, and medication information. - 6. A mechanism to assign a `modified_date` when events are updated. - - -## Conclusion -It looks like I’ve got my work cut out for me. In the next part of this series I’ll start implementing those requirements. - -LINK \ No newline at end of file diff --git a/docs/design/Screen Shot 2022-07-31 at 15.19.05.png b/docs/design/Screen Shot 2022-07-31 at 15.19.05.png deleted file mode 100644 index 97d21de..0000000 Binary files a/docs/design/Screen Shot 2022-07-31 at 15.19.05.png and /dev/null differ diff --git a/docs/design/design_doc.md b/docs/design/design_doc.md deleted file mode 100644 index 2b341c7..0000000 --- a/docs/design/design_doc.md +++ /dev/null @@ -1,294 +0,0 @@ -# Narcotics Tracker Design Document - -| Version | Created | Updated | -| :------ | :--------- | :--------- | -| 0.1.0 | 08/01/2022 | 08/24/2022 | - -{{TOC}} - -## Summary - -Welcome to the **Narcotics Tracker**! This software is designed to assist -controlled substance agents working at EMS agencies with inventory tracking and -periodic reporting of controlled substance activities to their governing -agencies. - -#### Motivation - -I am a controlled substance agent working in New York State. The tracking of -controlled substances is complicated and requires multiple forms and processes. -I have wished for a single place where all of that information can be stored -and easily accessed. This software is intended to fill that need. - -### Goals - -The **Narcotics Tracker** will provide a single place to enter all changes to -an agency's controlled substance inventory. Each change in the inventory can be -entered periodically where the data can be queried to return information -required for reporting and compliance with tracking specifications. - -##### Project Specifications - - 1. Inventory tracking of all controlled substances used by an EMS agency. - 2. Ability to create agency-specific medications. - 3. Tracking of medication lots as they as disbursed to sub-stocks. - 4. Tracking of medication orders. - 5. Tracking of medication destruction. - 6. Tracking of medication administration. - 7. Tracking of medication waste. - 8. Built-in reports which can be generated at the users' request and fulfill the New York State and DEA requirements. - 9. A simple but powerful console user interface. - 10. A graphical user interface. - -I am a self-taught programmer looking to improve my knowledge and experience in -soft-ware design by building projects which have practical applications. - -##### Personal Learning Goals - - 1. Increase my knowledge and experience with Python. - 2. Learn about Object Oriented Programming. - 3. Practice and gain experience with Test Driven Development - 4. Gain knowledge on the storage, and manipulation, of data and the use of databases. - 5. Potentially branch out into GUI development with Python or different languages as necessary. - 6. To put out a product that I may be able to use to help generate extra income through licensing and service contracts. - -## Design Discussion and Alternatives - ---- - -### Development Roadmap / Progress - -I'm not entirely sure where the best place to begin is. I do not have enough -experience to know how to design this kind of software. I'll be using a lot of -trial and error. Here is my imagined Development Path. - -- [x] Medication Creation and Management -- [x] Builder Pattern -- [x] Communication with a Database. -- [ ] Order Management. -- [ ] Lot Management. -- [ ] Destruction Management. -- [ ] Medication Use and Waste Management. -- [ ] Inventory Management -- [ ] Controlled Substance Agent Account Management. -- [ ] Report Generation -- [ ] Console User Interface -- [ ] Graphical User Interface - -### Medication Creation and Management - -To track the inventory of controlled substance medications a model of -the medications has to be built within the program. I decided to start the -project by building a module to handle the creation and implementation of -medications. - -Medications will be similar across EMS agencies but the specific dosages, -concentrations and other attributes of the meds will vary. There are many -specifics for controlled substance medications but I narrowed it down to 7 -medication-specific attributes and 5 which will be important to work with -the medication as part of the database. - -###### Medication-Specific Attributes - - - name (str): The name of the medication. - - - container_type (containers.Container): The type of container the medication comes in. - - - fill_amount (float): The amount of the solvent in the container. Measured in milliliters (ml). - - - dose (float): The amount of medication in the container. - - - preferred_unit (units.Unit): The unit of measurement the medication is commonly measured in. - - - concentration (float): The concentration of the medication to its solvent. - - - status (medication_status.MedicationStatus): The status of the medication. - -###### Database-Specific Attributes - - - medication_id (int): The numeric identifier of the medication in the database. - - - code (str): The unique identifier for the specific medication. - - - created_date (str): The date the medication was first entered into the database. - - - modified_date (str): The date the medication was last modified in the database - - - modified_by (str): The user who last modified the medication in the database. - -Five main requirements for controlled substance medications were -identified. - -###### Medication Behaviors - - 1. Creation of new medications by users. - 2. Saving medications within the database. - 3. Loading of saved medications. - 4. Updating of saved medications. - 5. Deletion of medications from the database. - -#### Discussion - -##### Attributes - -**The NDC Number was removed from the list of attributes.** The NDC number is -only important for medication destruction. NDC Numbers change between -manufacturers and concentrations for the same medication. NDC Numbers may not -be tracked at all within the Narcotics Tracker, if they are going to be tracked -they will be part of a different module. - ---- - -**The Box Quantity was removed from the list of attributes.** Box Quantity (how -many containers come in the box) will be important for Orders and Lot -Management but not as part of the medication module. - -##### Medication Data Structures - -There were tons of ways to represent medications within the **Narcotics -Tracker**. Ordered lists and dictionaries are simple and would fulfill most of -the requirements. As of version 0.1.0 dictionaries are used to load medications -as objects from data stored in the database and lists are used in a script to -quickly create the medications I use at my agency. **I decided that -using classes and objects would be the best way for me to achieve the results I -wanted with this project and help me improve my object-oriented programming -skills.** - -##### The Builder Pattern - -Since I decided to go with objects as the data structure for medications I -needed a way to simplify the creation of medications for myself as the -developer and for the users. With twelve total attributes, it would be easy to -assign values to the wrong attributes, forget attributes, and potentially build -medications that would be unusable. - -**I employed the builder pattern to separate the creation of -medications into smaller, easier-to-understand steps. Using the builder pattern -does add complexity to the code it also adds the flexibility to use the same -approach to different objects used later within the Narcotics Tracker.** - -##### Enums vs. Vocabulary Control Tables - -There are limited options for the types of containers a controlled substance -medication might come in. The status of each medication and its preferred -dosage unit also have limited options. - -As of the version 0.1.0 release Enums were created for each of those three -attributes and are handled through Python and its objects. It was brought to -my attention that this will limit the flexibility for users who may need to -create custom options for these attributes. - -**In a future release these Enums will be converted into vocabulary control -tables within the database. This will allow users to create new statuses, -containers, and units as needed for their agency.** - -##### Medication Deletion - -Deleting medications will likely cause issues in long-term record -keeping. Attributes for medications that are no longer in use are important -when pulling records from previous periods when they were in use. Deletion of -medication is likely not going to be a feature that the users will have access -to. **Deleting Medications is important enough for this option to be available -during the development of the Narcotics Tracker that I have chosen to build it.** -It can be removed later if deemed unnecessary. - -### Communication with a Database - -Databases are used everywhere in software and I’ve never worked with one. The -**Narcotics Tracker** is an ideal project for me to dip my toes in and build my -understanding of databases. Other methods for storing data were considered such -as writing to JSON Files, CSV Files, and Pickle Files but the strengths of using -a database made it an obvious choice. - -The only attribute required for the database is the connection to the database -file. A - -###### Database Attributes - - - database_connection (sqlite3.Connection): The connection to the database file. - -###### Database Behaviors - - 1. Creation and connection with a database file. - 2. Creation of tables within the database. - 3. Querying table names from the database file. - 4. Querying column names from database tables. - 5. Updating tables within the database. - 6. Deleting tables from the database. - 7. Writing data to the database. - 8. Reading data from the database. - 9. Loading objects from data saved in the database. - 10. Setting of the dates when saving objects to the database. - -#### Discussion - -##### Attributes - -There were not many other attributes I thought would be important to store -within the database objects. **One attribute which may be added later is the -path to the data directory.** Currently the path is hard coded into the -methods, but it might make sense to give users an option on where they want -their database files to be stored. - -##### Database Choice - -**I decided to use SQLite3 for this project.** SQLite3 is built into Python. It -requires no configuration or external services, and I already watched a course -on using it, so there! - -##### Database Tables - -Multiple tables will be required for the inventory tracking portion of this -project: - - 1. Medication Table - 2. Order Table - 3. Lot Table - 4. Destruction Table - 5. Administration Table - 6. Inventory Table - 7. User / Agent Table - -In addition, vocabulary control tables will be required to help set the specific -types of data and events in the project: - - 1. Containers Table - 2. Medication Statuses Table - 3. Dosage Units Table - 4. Events Table - -Additional tables may be identified as the project progresses. - -### Tracking of Controlled Substance Orders - -### Lot Management - -To Be Written. - -### Destruction Management - -To Be Written. - -### Medication Use and Waste Management - -To Be Written. - -### Inventory Management - -To Be Written. - -### Controlled Substance Agent Account Management - -To Be Written. - -### Report Generation - -To Be Written. - -### Console User Interface - -To Be Written. - -### Graphical User Interface - -To Be Written. \ No newline at end of file diff --git a/docs/design/mermaid-diagram-2022-08-31-172347.png b/docs/design/mermaid-diagram-2022-08-31-172347.png deleted file mode 100644 index 685d2f4..0000000 Binary files a/docs/design/mermaid-diagram-2022-08-31-172347.png and /dev/null differ diff --git a/docs/design/mermaid-diagram-2022-08-31-172415.svg b/docs/design/mermaid-diagram-2022-08-31-172415.svg deleted file mode 100644 index bbb6bf0..0000000 --- a/docs/design/mermaid-diagram-2022-08-31-172415.svg +++ /dev/null @@ -1 +0,0 @@ -INVENTORY_TABLEadjustment_idadjustment_dateevent_codemedication_codequantity_in_mcgreporting_period_idreference_idcreated_datemodified_datemodified_byMEDICATION_TABLEmedication_idintegerPrimary Keymedication_codestringForeign Keynamestringcontainer_typestringfill_amountfloatdose_in_mcgfloatpreferred_unitstringForeign Keyconcentrationfloatmedication_statusstringcreated_dateintegermodified_dateintegermodified_bystringEVENT_TYPES_TABLEevent_idintegerPrimary Keyevent_codestringForeign Keyevent_namestringdescriptionstringoperatorinteger+1 or -1created_dateintegermodified_dateintegermodified_bystringREPORTING_PERIODS_TABLEperiod_idintegerPrimary Keystarting_dateintegerending_dateintegercreated_dateintegermodified_dateintegermodified_bystringUSERDATABASEhashashashashashashashashashasreferencesreferencesreferencesused to calculatesetssetssetssetsAssignsAssigns \ No newline at end of file diff --git a/docs/design/reporting_period_table.png b/docs/design/reporting_period_table.png deleted file mode 100644 index e84f3af..0000000 Binary files a/docs/design/reporting_period_table.png and /dev/null differ diff --git a/docs/design_document/00_design_overview.md b/docs/design_document/00_design_overview.md new file mode 100644 index 0000000..68b5d93 --- /dev/null +++ b/docs/design_document/00_design_overview.md @@ -0,0 +1,46 @@ +# 0.0 - Project Overview + +| Section | Version | Created | Updated | +| :------ | :------ | :--------- | :--------- | +| 0.0 | 0.2.5 | 08/01/2022 | 10/25/2022 | + +{{TOC}} + +The **Narcotics Tracker** is a python application created to assisted with the inventory management of controlled substance medications for EMS agencies. It is currently in development. For the most up-to-date information regarding this project please review the [GitHub Repository](https://github.com/ScottSucksAtProgramming/narcotics_tracker). + +## Development Team: +**Lead Developer: Scott Kostolni** +- [GitHub](https://github.com/ScottSucksAtProgramming/narcotics_tracker/commits?author=ScottSucksAtProgramming) +- [Linkedin](https://www.linkedin.com/in/scottkostolni/) + +## Frameworks and Technologies +- [Python](https://www.python.org) +- [Pendulum](https://pendulum.eustace.io) +- [SQlite3](https://www.sqlite.org/index.html) + +## Specifications and Requirements + + 1. Inventory tracking of controlled substance activities including: + - Ordering of controlled substances. + - Provider administration and waste of controlled substances. + - Destruction of controlled substances through reverse distributors and pharmacies. + - Loss of controlled substance medications. + - Lot disbursement. + 2. Configureable DataItems to accomodate a variety EMS agencies. + - Medications + - Inventory Adjustments + - Event Types + - Reporting Periods + - Statuses + - Units + - Purchase Orders + - Destruction Orders + 3. Report generation + - Breakdown of inventory adjustments. + - On demand calculation of inventory amounts on hand. + - Conversion between different units of mass and volume. + 4. Command Line User Interface + 5. Console User Interface + 6. Graphical User Interface + +[Design Document Table of Contents](01_table_of_contents.md) \ No newline at end of file diff --git a/docs/design_document/01_table_of_contents.md b/docs/design_document/01_table_of_contents.md new file mode 100644 index 0000000..aea7005 --- /dev/null +++ b/docs/design_document/01_table_of_contents.md @@ -0,0 +1,13 @@ +# 0.1 - Table of Contents + +| Section | Version | Created | Updated | +| :------ | :------ | :--------- | :--------- | +| 0.1 | 0.2.5 | 10/25/2022 | 10/25/2022 | + +- [Project Overview](00_design_overview.md) +- [Table of Contents](01_table_of_contents.md) +- [Directory and File Structure](02_directory_and_file_structure.md) +- Program Structure + - Persistence + - Use Cases + - User Interface \ No newline at end of file diff --git a/docs/design_document/02_directory_and_file_structure.md b/docs/design_document/02_directory_and_file_structure.md new file mode 100644 index 0000000..e953fcf --- /dev/null +++ b/docs/design_document/02_directory_and_file_structure.md @@ -0,0 +1,54 @@ +# 0.2 - Directory and File Structure + +| Section | Version | Created | Updated | +| :------ | :------ | :--------- | :--------- | +| 0.1 | 0.2.5 | 10/25/2022 | 10/25/2022 | + +{{TOC}} + + + +## Directory and File Structure +- `narcotics_tracker/` + - Project Repository Folder + - `LICENSE.txt` + - License Information + - `README.md` + - General Project Information + - `assets/` + - Media items used throughout project development. + - `data/` + - Project database directory. + - `docs/` + - `design_document/` + - Markdown files outlining the design options and decisions made for the project. + - `release_notes/` + - Markdown files outlining the changes to the project throughout its development. + - `style_guide/` + - Markdown files outline the style guide for this project. + - `narcotics_tracker/` + - Project source code directories. + - `database.py` + - Handles communication directly with the SQLite3 Database. + - `builders/` + - Handles the construction of DataItems. + - `commands/` + - Handles higher-level program functions by using the Command design pattern. + - `items/` + - Defines the DataItems which enable inventory tracking. + - `scripts/` + - Miscellaneous scripts for setup and testing. + - `setup/` + - Contains setup and configuration files. + - `utils/` + - Miscellaneous helper and utility modules. + - `tests/` + - Testing Suite + - `integration/` + - Tests higher level features which require inter-related parts of the application. + - `unit/` + - Tests lower level features. + - `venv/` + - Project virtual environment files. + +[Design Document Table of Contents](01_table_of_contents.md) \ No newline at end of file diff --git a/docs/design_document/10_project_design_architechture.md b/docs/design_document/10_project_design_architechture.md new file mode 100644 index 0000000..604b367 --- /dev/null +++ b/docs/design_document/10_project_design_architechture.md @@ -0,0 +1,57 @@ +# 1.0 - Project Design Architecture + +| Section | Version | Created | Updated | +| :------ | :------ | :--------- | :--------- | +| 1.0 | 0.2.5 | 10/25/2022 | 10/25/2022 | + +{{TOC}} + +## Code Structure + +### Persistence Layer + +Handles the storage of data within the SQLite3 Database. + +### Use Case Layer +Contains the buisness logic which enables inventory management. + +#### DataItems +The objects which enable inventory management of controlled substances. + +###### Adjustments +###### Events +###### [Medications](01_medications.md) +###### Reporting Periods +###### Statuses +###### Units + +#### Commands +Objects which handle the higher-level concerns of inventory management. + +###### Inventory Management +###### Order Management +Not Yet Implemented +###### Lot Disbursement Management +Not Yet Implemented +###### Provider Managemement +Not Yet Implemented +###### Agent Management +Not Yet Implemented + + +#### Reports + +Not Yet Implemented + + +### User Facing Layer + + +#### Command Line User Interface +Not Yet Implemented +#### Console User Interface +Not Yet Implemented +#### Graphical User Interface +Not Yet Implemented + +[Design Document Table of Contents](01_table_of_contents.md) diff --git a/docs/design/02_database.md b/docs/design_document/persistence/02_database.md similarity index 100% rename from docs/design/02_database.md rename to docs/design_document/persistence/02_database.md diff --git a/docs/design/database_design_v1.pdf b/docs/design_document/persistence/database_design_v1.pdf similarity index 100% rename from docs/design/database_design_v1.pdf rename to docs/design_document/persistence/database_design_v1.pdf diff --git a/docs/design/database_design_v2.pdf b/docs/design_document/persistence/database_design_v2.pdf similarity index 100% rename from docs/design/database_design_v2.pdf rename to docs/design_document/persistence/database_design_v2.pdf diff --git a/docs/design/events_table_design.png b/docs/design_document/persistence/events_table_design.png similarity index 100% rename from docs/design/events_table_design.png rename to docs/design_document/persistence/events_table_design.png diff --git a/docs/design/high_level_overview.pdf b/docs/design_document/persistence/high_level_overview.pdf similarity index 100% rename from docs/design/high_level_overview.pdf rename to docs/design_document/persistence/high_level_overview.pdf diff --git a/docs/design/inventory_table_design.png b/docs/design_document/persistence/inventory_table_design.png similarity index 100% rename from docs/design/inventory_table_design.png rename to docs/design_document/persistence/inventory_table_design.png diff --git a/docs/design/medication_table_design 2.png b/docs/design_document/persistence/medication_table_design 2.png similarity index 100% rename from docs/design/medication_table_design 2.png rename to docs/design_document/persistence/medication_table_design 2.png diff --git a/docs/design/medication_table_design.png b/docs/design_document/persistence/medication_table_design.png similarity index 100% rename from docs/design/medication_table_design.png rename to docs/design_document/persistence/medication_table_design.png diff --git a/docs/design/03_inventory.md b/docs/design_document/use_case/03_inventory.md similarity index 100% rename from docs/design/03_inventory.md rename to docs/design_document/use_case/03_inventory.md diff --git a/docs/design/07_orders.md b/docs/design_document/use_case/07_orders.md similarity index 100% rename from docs/design/07_orders.md rename to docs/design_document/use_case/07_orders.md diff --git a/docs/design/11_reports.md b/docs/design_document/use_case/11_reports.md similarity index 100% rename from docs/design/11_reports.md rename to docs/design_document/use_case/11_reports.md diff --git a/docs/design_document/use_case/CommandStructure_DBItems_to_Database.md b/docs/design_document/use_case/CommandStructure_DBItems_to_Database.md new file mode 100644 index 0000000..c45c441 --- /dev/null +++ b/docs/design_document/use_case/CommandStructure_DBItems_to_Database.md @@ -0,0 +1,79 @@ +Mermaid Code! + +```mermaid +classDiagram + +class DataItem { + <> + +String table + +Dict column_info + +Integer id + +Integer created_date + +Integer modified_date + +String modified_by + -__str__() String +} + + +class Adjustment { + +Integer adjustment_date + +String event_code + +String medication_code + +Integer adjustment_amount + +String reference_id + -__str__() String +} + +class Event { + +String event_code + +String event_name + +String description + +Integer modifier + +__str__() String +} + + +class Medication { + +String medication_code + +String medication_name + +Float fill_amount + +Float medication_amount + +String preferred_unit + +Float concentration + +String status + -__str__() String +} + + +class ReportingPeriod { + +int start_date + +int end_date + +String status + -__str__() String +} + +class Status { + +String status_code + +String status_name + +String description + -__str__() String +} + +class Unit { + +String unit_code + +String unit_name + +Integer decimals + -__str__() String +} + + + +DataItem <|-- Event : Inherits From +DataItem <|-- Adjustment : Inherits From +DataItem <|-- Medication : Inherits From +DataItem <|-- ReportingPeriod : Inherits From +DataItem <|-- Status : Inherits From +DataItem <|-- Unit : Inherits From + + +``` diff --git a/docs/design/01_medications.md b/docs/design_document/use_case/Data Items/01_medications.md similarity index 100% rename from docs/design/01_medications.md rename to docs/design_document/use_case/Data Items/01_medications.md diff --git a/docs/design_document/use_case/Data Items/Class Diagram - Adjustment.svg b/docs/design_document/use_case/Data Items/Class Diagram - Adjustment.svg new file mode 100644 index 0000000..f2f4ccb --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - Adjustment.svg @@ -0,0 +1 @@ +
Inherits
«Interface»
DataItem
+String table
+Dict column_info
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Adjustment
+Integer adjustment_date
+String event_code
+String medication_code
+Float adjustment_amount
+String reference_id
+__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Class Diagram - DataItem.svg b/docs/design_document/use_case/Data Items/Class Diagram - DataItem.svg new file mode 100644 index 0000000..e58d50f --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - DataItem.svg @@ -0,0 +1 @@ +
«Interface»
DataItem
+String table
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Class Diagram - Event.svg b/docs/design_document/use_case/Data Items/Class Diagram - Event.svg new file mode 100644 index 0000000..72d30ec --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - Event.svg @@ -0,0 +1 @@ +
Inherits
«Interface»
DataItem
+String table
+Dict column_info
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Event
+String event_code
+String event_name
+String description
+Integer modifier
+__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Class Diagram - Medication.svg b/docs/design_document/use_case/Data Items/Class Diagram - Medication.svg new file mode 100644 index 0000000..f6eacad --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - Medication.svg @@ -0,0 +1 @@ +
Inherits
«Interface»
DataItem
+String table
+Dict column_info
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Medication
+String medication_code
+String medication_name
+Float fill_amount
+Float medication_amount
+String preferred_unit
+Float concentration
+String status
-__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Class Diagram - ReportingPeriod.svg b/docs/design_document/use_case/Data Items/Class Diagram - ReportingPeriod.svg new file mode 100644 index 0000000..0face03 --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - ReportingPeriod.svg @@ -0,0 +1 @@ +
Inherits
«Interface»
DataItem
+String table
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Adjustment
+Integer adjustment_date
+String event_code
+String medication_code
+Integer adjustment_amount
+String reference_id
+Integer reporting_period_id
-__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Class Diagram - Status.svg b/docs/design_document/use_case/Data Items/Class Diagram - Status.svg new file mode 100644 index 0000000..aaa4e14 --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - Status.svg @@ -0,0 +1 @@ +
Inherits
«Interface»
DataItem
+String table
+Dict column_info
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Status
+String status_code
+String status_name
+String description
-__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Class Diagram - Unit.svg b/docs/design_document/use_case/Data Items/Class Diagram - Unit.svg new file mode 100644 index 0000000..28fb103 --- /dev/null +++ b/docs/design_document/use_case/Data Items/Class Diagram - Unit.svg @@ -0,0 +1 @@ +
Inherits
«Interface»
DataItem
+String table
+Dict column_info
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Unit
+String unit_code
+String unit_name
+Integer decimals
-__str__() : String
\ No newline at end of file diff --git a/docs/design_document/use_case/Data Items/Relationship Diagram - DataItems.svg b/docs/design_document/use_case/Data Items/Relationship Diagram - DataItems.svg new file mode 100644 index 0000000..52727b8 --- /dev/null +++ b/docs/design_document/use_case/Data Items/Relationship Diagram - DataItems.svg @@ -0,0 +1 @@ +
Inherits From
Inherits From
Inherits From
Inherits From
Inherits From
Inherits From
«Interface»
DataItem
+String table
+Dict column_info
+Integer id
+Integer created_date
+Integer modified_date
+String modified_by
-__str__() : String
Adjustment
+Integer adjustment_date
+String event_code
+String medication_code
+Integer adjustment_amount
+String reference_id
-__str__() : String
Event
+String event_code
+String event_name
+String description
+Integer modifier
+__str__() : String
Medication
+String medication_code
+String medication_name
+Float fill_amount
+Float medication_amount
+String preferred_unit
+Float concentration
+String status
-__str__() : String
ReportingPeriod
+Integer start_date
+Integer end_date
+String status
-__str__() : String
Status
+String status_code
+String status_name
+String description
-__str__() : String
Unit
+String unit_code
+String unit_name
+Integer decimals
-__str__() : String
\ No newline at end of file diff --git a/docs/design/medication_feature.md b/docs/design_document/use_case/Data Items/medication_feature.md similarity index 100% rename from docs/design/medication_feature.md rename to docs/design_document/use_case/Data Items/medication_feature.md diff --git a/docs/design_document/use_case/command_structure_for_db_items.svg b/docs/design_document/use_case/command_structure_for_db_items.svg new file mode 100644 index 0000000..7ad5b6e --- /dev/null +++ b/docs/design_document/use_case/command_structure_for_db_items.svg @@ -0,0 +1 @@ +
Inherits From
Inherits From
Inherits From
Inherits From
Inherits From
Inherits From
Implements
Uses
Acts On
«Abstract Base Class»
DatabaseItems
+ String Table_Name
+ Dict Column_Info
+ Integer ID
+ Integer Created_Date
+ Integer Modified_Date
+ String Modified_By
+Save(CommandInterface)
+Load(CommandInterface)
+Update(CommandInterface)
«DataClass / Invoker»
Adjustments
+ Unixepoch AdjustmentDate
+ String EventCode*
+ String MedicationCode*
+ Integer AdjustmentAmount
+ String ReferenceID
«DataClass / Invoker»
Events
+ String Code
+ String Name
+ String Description
+ Integer Modifier
«DataClass / Invoker»
Medications
+ String MedicationCode
+ String MedicationName
+ Float FillAmount
+ Float MedicationAmount
+ Unit PreferredUnit*
+ Float Concentration
«DataClass / Invoker»
ReportingPeriods
+ Unixepoch StartDate
+ Unixepoch EndDate
«DataClass / Invoker»
Statuses
+ String StatusCode
+ String StatusName
+ String Description
«DataClass / Invoker»
Units
+ String UnitCode
+ String UnitName
+ Integer Decimals
«Receiver»
Database
+ Table Events
+ Table Inventory
+ Table Medications
+ Table ReportingPeriods
+ Table Statuses
+ Table Units
«Protocol»
CommandInterface
+execute()
Commands
+ CommandInterface
\ No newline at end of file diff --git a/docs/design/user_processes.md b/docs/design_document/use_case/user_processes.md similarity index 100% rename from docs/design/user_processes.md rename to docs/design_document/use_case/user_processes.md diff --git a/docs/releases/Changes for Version 0.1.0.md b/docs/release_notes/Changes for Version 0.1.0.md similarity index 100% rename from docs/releases/Changes for Version 0.1.0.md rename to docs/release_notes/Changes for Version 0.1.0.md diff --git a/docs/releases/Changes for Version 0.2.0.md b/docs/release_notes/Changes for Version 0.2.0.md similarity index 100% rename from docs/releases/Changes for Version 0.2.0.md rename to docs/release_notes/Changes for Version 0.2.0.md diff --git a/docs/release_notes/Changes for Version 0.2.5.md b/docs/release_notes/Changes for Version 0.2.5.md new file mode 100644 index 0000000..d6ebeb6 --- /dev/null +++ b/docs/release_notes/Changes for Version 0.2.5.md @@ -0,0 +1,60 @@ +# Introducing Narcotics Tracker v0.2.5. + +| Version | Release Date | Audience | +| :------ | :----------- | :--------- | +| 0.2.5 | 10/31/2022 | Developers | + +**Message from ScottSucksAtProgramming:** + +> Welcome to the Newly Refactored version of the Narcotics Tracker. After +> version 0.2.0 the code was quite a bit of a mess. I focused on restructuring +> the code and reducing coupling. The ultimate effect is code which is much +> more pleasant to work with and easier to understand. Read on to see the +> larger changes. + +## Structural Improvements + +### Interfaces + +Most packages within the Narcotics Tracker now include an interface specifying +how new modules should be created. Interfaces are located in a separate +sub-package. + +### Design Patterns + +The Builder Pattern had already been implemented to make construction of +DataItems easier. With this update the Command Pattern was also implemented +allowing for the decoupling many modules. All commands share the same interface +allowing for easy creation of new commands. Look at the documentation in the +Commands Package for more information. + +### Inheritance and DataClasses + +The Items Package saw an overhaul in is structure. Each of the six DataItems +inherit from a DataItem superclass. DataItems are no longer responsible for +saving and loading themselves from the database, their only concern is to store +their data. Each DataItem is now written as a dataclass. These two change make +the code much simpler to read. + +## New Functionality + +### The Service Provider + +The Utilities Package was removed and replaced with the services package. As of +this release three services are included in this package. The SQLiteManager +provides the persistence service which stores and retrieves information from +the SQLite3 database. To manage dates and times the DateTimeManager provides +the datetime service; This object is responsible for providing datetime +information and converting between human readable dates and unix timestamps. +The conversion service, provided by the ConversionManager, converts between +different units of mass and volume. + +The Service Provider module instantiates each service as needed. It is a single +point of access for all current and future services. The Service Provider also +allows for each service manager to be replaced with new or different services +as needed. + +## Next Up! + +The next release will see the creation of the reporting functionality and a set +of general reports which are frequently used for narcotics management. diff --git a/narcotics_tracker/.vscode/settings.json b/narcotics_tracker/.vscode/settings.json new file mode 100644 index 0000000..e6e7934 --- /dev/null +++ b/narcotics_tracker/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, + "hide-files.files": [] +} \ No newline at end of file diff --git a/narcotics_tracker/__init__.py b/narcotics_tracker/__init__.py index 5ccef3c..e7f5e17 100644 --- a/narcotics_tracker/__init__.py +++ b/narcotics_tracker/__init__.py @@ -1,10 +1,11 @@ """Tracks the inventory of controlled substances used at EMS agencies. -Version: 0.2.0 +#* Title: Narcotics Tracker +#* Version: 0.2.5 +#* Author: Scott Kostolni - (https://github.com/ScottSucksAtProgramming) -Author: Scott Kostolni - (https://github.com/ScottSucksAtProgramming) +#* Special Thanks: -Special Thanks: Mom, thanks for being my software design mentor and entertaining my onslaught of questions. @@ -12,85 +13,132 @@ for reading my documentation to ensure I sound a little less like an idiot than I actually am. -Purpose: +#* Purpose: + Welcome to the Narcotics Tracker! This software is designed to assist controlled substance agents working at EMS agencies with inventory tracking and periodic reporting of controlled substance activities to their governing agencies. -Motivation: +#* Motivation: + I am a controlled substance agent working in New York State. The tracking of controlled substances is complicated and requires multiple forms and processes. I have wished for a single place where all of that information can be stored and easily accessed. This software is intended to fill that need. -Current Completed Features: - Database Object Creation - Medications, Medication Containers, Units of - Measurement, Events, Inventory Adjustments, Reporting Periods and Statuses - can be created using the Builder Modules for each item. - - Database and Table Creation - Tables have been defined for all Database - Objects and can be stored. The Database Class now functions as a context - manager for better resource management. - -Planned Features: - Inventory Tracking (v0.2.0 - alpha) - Completed! - - Code Architecture Improvement (v0.2.5 - Alpha) - - Command Line Tools (v1.0.0) - - Order Tracking (v1.1.0) - - Lot Tracking (v1.2.0) - - Destruction Tracking (v1.3.0) - - User and Agent Management (v1.4.0) - - Report Generation (v1.5.0) - - Console Interface (v1.6.0) - - GUI Interface (v1.7.0) - -Packages: - Builders: Assists with the building of complex objects. - - Enums: Package Removed in v0.2.0 - alpha. - - Setup: Contains standard items for populating the database. - - Utils: Contains utility functions used in the software. - -Modules: - - Containers: Contains implementation and representation of Medication - Containers. - - Database: Defines the database model for the narcotics tracker. - - Events: Contains the implementation and representation of Events. - - Inventory: Contains the implementation and representation of Inventory - Adjustments. - - Medications: Contains the implementation and representation of Medication - Objects. - - Reporting Periods: Contains the implementation and representation of - Reporting Period Objects. - - Statuses: Contains the implementation and representation of Object - Statuses. - - Units: Contains implementation and representation of Units of Measurement. - - -Release Notes: - -Version 0.1.0 - https://github.com/ScottSucksAtProgramming/narcotics_tracker/releases/tag/v0.1.0-alpha - -Version 0.2.0 - https://github.com/ScottSucksAtProgramming/narcotics_tracker/releases/tag/v0.2.0-alpha +#* Current Completed Features: + + Custom Object Creation - Medications, Units of Measurement, Events, + specific Inventory Adjustments, Reporting Periods and Statuses can be + created allowing any EMS agency to customize the Narcotics Tracker to + their policies and procedures. These objects are collectively referred to + as DataItems as they are items which live in the database. + + Persistent Storage - The built-in SQLite3 Database library has been used + as the main data repository for the Narcotics Tracker. Tables have been + created to for each of the six DataItems. The Inventory Table serves as + the main table for the database; It tracks each individual change to the + stock of a medication, called an Adjustment. Adjustments use data stored + in the Events Table, among others, to calculate how the adjustment affects + the stock and which medications are affected by it. The Medications Table + stores information on the controlled substances used by an EMS agency + including their concentration and preferred unit of measurement. These + data points are used to tally medication totals and calculate data + required when reporting to oversight agencies. + + Utility Services - Multiple utilities are used to manage the inventory of + controlled substances. The Service Provider feature provides quick and + easy access to these services and provides an interface that new services + can make use of without requiring changes to the code that relies on a + service. + + Flexible Design and Architecture - In the most recent update, the + structure of the Narcotics Tracker was rebuilt from the ground up to + improve the readability of the code and reduce its fragility. As a result + the code is well structured, easier to work with, and much more + extensible. + +#* Planned Features: + + #✓ Medications and Initial Development (v0.1.0 - Alpha) - Completed! + #✓ Inventory Tracking (v0.2.0 - Alpha) - Completed! + #✓ Code Architecture Improvement (v0.2.5 - Alpha) - Completed!! + #TODO Basic Report Generation (v0.3.0 - Alpha) - Next Up!! + #! Command Line Tools (v0.0.0 - Beta) + #! Order Tracking (v0.1.0 - Beta) + #! Lot Tracking (v0.2.0 - Beta) + #! Destruction Tracking (v0.3.0 - Beta) + #! User and Agent Management (v0.4.0 - Beta) + #! Command Line Tools Redux (v0.5.0 - Beta) + #! Console Interface (v0.6.0) + #! GUI Interface (v0.7.0) + +#* Meet the Players: + + #* DataItems and Builders + + DataItems are individual objects which are stored in the database and + enable inventory management. They contain numerous attributes and the + Builder Design Pattern was used to help make constructing DataItems + easier. Each DataItem has its own builder which provides a step-wise + approach to assigning attributes and constructing the object. In + future updates Director Objects will be provided to walk end users + through the creation of DataItems. + + - Adjustments record specific changes to the stock of a medication. + + - Events classify the types of changes which commonly occur and + determine if amounts are added or removed from the stock. + + - Medications store relevant information about the controlled + substances. Adjustments must specify which medication(s) were + affected. + + - Reporting Periods allow for adjustments to be organized by their + date and are used to determine which adjustments need to be + reported. + + - Statuses provide additional information for Medications, Reporting + Periods and future additions to the Narcotics Tracker. + + - Units store information on how a medication is measured and are + integral to completing reports for oversight agencies. + + #* The Services + + Services provide utilities to help with the management of the + narcotics inventory. The Service Provider offers an easy way for these + services to be accessed. + + - The Persistence Service communicates directly with the SQLite + database. It stores items in the appropriate tables returns + requested data. + + - The DateTime Service provides date and time information and converts + between human readable dates and the unix timestamps which are + stored in the database. + + - The Conversion Service converts medication amounts between various + units of mass and volume. + + #* Commands + + In order to increase the flexibility of the Narcotics Tracker the + Command Design Pattern was implemented. Commands provide access to + simple and complex activities through a shared interface. + + Each command allows for the specification of its intended receiver + during initialization. To trigger the command its 'execute' method is + called. Any required information can be passed into the execute method + which will pass it on to its target to complete the command. + Additional commands can be easily created using this interface. + + +#* Release Notes: + + Version 0.1.0 - https://github.com/ScottSucksAtProgramming/narcotics_tracker/releases/tag/v0.1.0-alpha + Version 0.2.0 - https://github.com/ScottSucksAtProgramming/narcotics_tracker/releases/tag/v0.2.0-alpha + Version 0.2.5 - https://github.com/ScottSucksAtProgramming/narcotics_tracker/releases/tag/v0.2.5-alpha """ diff --git a/narcotics_tracker/builders/__init__.py b/narcotics_tracker/builders/__init__.py index 2415e0d..8a14bd8 100644 --- a/narcotics_tracker/builders/__init__.py +++ b/narcotics_tracker/builders/__init__.py @@ -1,93 +1,69 @@ -"""Assists with building complex objects for the narcotics tracker. +"""Contains the builders for the DataItems used in the Narcotics Tracker. -Many objects in the narcotics tracker are complex and have a large number of -attributes. The builder design pattern was used to separate the creation of -the objects from their representations allowing objects to be created in a -modular fashion. +There are multiple DataItems in the Narcotics Tracker. Each one stores various +bits of information which enable inventory tracking. Each DataItem also +inherits from the DataItem class which adds more attributes. This makes these +items difficult to build with large constructors. -The builder package contains two types of builders: Abstract builders, which -serve as templates for the Concrete builders. The Concrete builders implement -the building process. Abstract builders are postfixed with the word -`template`. +This Package implements the Builder Pattern which helps solve this problem by +separating the creation of these object from the objects themselves. Instead +of using a DataItem's initializer a Builder has been created which constructs +the object in a stepwise fashion. Each Builder implements the interface +defined in a DataItemBuilder superclass. -Tests for the builders' package are located in the tests/unit/builders_test.py +Interfaces: -Modules: - - Abstract Builder Templates: - - adjustment_builder_template: Contains the template for the Adjustment - Builder. - - container_builder_template: Contains the template for the Container - Builder. - - event_builder_template: Contains the template for the Event Builder. - - medication_builder_template: Contains the template for the Medication - Builder. - - reporting_period_builder_template: Contains the template for the - Reporting Period Builder. - - status_builder_template: Contains the template for the Status Builder. - - unit_builder_template: Contains the template for the Unit Builder. - - Concrete Builder Modules: + Builder: Contains the interface for concrete DataItem builders. - adjustment_builder: Contains the concrete builder for the Adjustment - class. - - container_builder: Contains the concrete builder for the Container - class. - - event_builder: Contains the concrete builder for the Event class. - - medication_builder: Contains the concrete builder for the Medication - class. - - reporting_period_builder: ‌Contains the concrete builder for the - ReportingPeriod class - - status_builder: Contains the concrete builder for the Status class. - - unit_builder: Contains the concrete builder for the Unit class. - - -How to Use the Builders: +Modules: - 1. Import the builder for the object you want to build. + DataItemBuilder: Defines the builder for generic DataItems. Only meant to + be inherited from. - 2. If needed make a connection with the database using the - database.Database() context manager. Review documentation of Database - Module for more information. + Adjustment Builder: Handles the defining and building of Adjustment + Objects. - 3. Initialize the builder by assigning it to a variable and passing any - information required by its __init__ method. - - 4. Call the various 'set' methods and pass in the required information. - - 5. Call the builder's `build()` method and assign it to a variable. - -Example: - - ```python - from narcotics_tracker.builders import medication_builder - - med_builder = medication_builder.MedicationBuilder() - - med_builder.set_medication_id(None) - med_builder.set_name("Aspirin") - med_builder.set_code("ASA") - med_builder.set_fill_amount(10) - med_builder.set_container(containers.Container.AMPULE) - med_builder.set_dose_and_unit(10, units.Unit.MCG) - med_builder.set_status(medication_statuses.MedicationStatus.ACTIVE) - med_builder.set_created_date(None) - med_builder.set_modified_date(None) - med_builder.set_modified_by("SRK") + Event Builder: Handles the defining and building of Event Objects. + + Medication Builder: Handles the defining and building of Medication + Objects. + + Reporting Period Builder: Handles the defining and building of Reporting + Period Objects. - aspirin = med_builder.build() - ``` - """ + Status Builder: Handles the defining and building of Status Objects. + + Unit Builder: Handles the defining and building of Unit Objects. + +How To Use: + + When creating a DataItem, the builder for that item can be created and its + methods can be called to build the object piece by piece. When all pieces have + been constructed the `build` method will create return the object. After + calling the build method, the builder is reset with a fresh instance of the + DataItem object so it can be re-used if necessary. + + Example: + + ```python + fentanyl = ( + MedicationBuilder() + .set_table("medications") + .set_id(None) + .set_created_date(1666932094) + .set_modified_date(1666932094) + .set_modified_by("SRK") + .set_medication_code("fentanyl") + .set_medication_name("Fentanyl") + .set_fill_amount(2) + .set_medication_amount(100) + .set_preferred_unit("mcg") + .set_concentration(0.05) + .set_status("ACTIVE") + .build() + ) + ``` + + Review the documentation of specific builders for more information on + their usage and available methods. +""" diff --git a/narcotics_tracker/builders/adjustment_builder.py b/narcotics_tracker/builders/adjustment_builder.py index f583019..77dfc81 100644 --- a/narcotics_tracker/builders/adjustment_builder.py +++ b/narcotics_tracker/builders/adjustment_builder.py @@ -1,375 +1,147 @@ -"""Contains the concrete builder for the Adjustment class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. - -Adjustments are entries saved to the inventory table which add or remove -medication amounts from the stock. - -For more information on Adjustments look at the documentation for the -Inventory Module. - -For more information in communication with the database look at the -documentation for the Database Module. +"""Handles the defining and building of Adjustment Objects. Classes: - AdjustmentBuilder: Builds and returns Inventory Adjustment objects. + AdjustmentBuilder: Assigns attributes and returns Adjustment Objects. """ -import sqlite3 - -from narcotics_tracker import ( - database, - inventory, - medications, - reporting_periods, -) -from narcotics_tracker.builders import adjustment_builder_template -from narcotics_tracker.utils import unit_converter - - -class AdjustmentBuilder(adjustment_builder_template.Adjustment): - """Builds and returns Inventory Adjustment objects. - - The AdjustmentBuilder class is used to construct Inventory Adjustment - objects. There are two types of methods: 'set' methods can be called to - manually set attributes for the object; 'assign' methods perform - calculations and are used as part of the build method to assign other - attributes like the created date, or unit conversions. - - Look at the Adjustment Class documentation in the Inventory Module for - more information on how to use the Adjustment objects. - - How To Use: - - 1. Create a database connection using the database.Database() context - manager. - - `with database.Database('filename.db') as db:` - - 2. Initialize the builder by assigning it to a variable and passing a - database connection: - - `adj_builder = adjustment_builder.AdjustmentBuilder(db)` - - 3. Call the following methods and pass the required values: - - `set_adjustment_date()`; `set_event_code()`; - - `set_medication_code()`; `set_adjustment_amount()`; - - `set_reference_id()`; and `set_modified_by()`; - - 4. Call the `build()` method to return an Adjustment object. - - The `build()` method will convert the adjustment_amount to the - amount_in_mcg and assign the correct reporting_period based on the - adjustment_date. If you would like assign these manually it's - recommended to do so after the object has been created. - - Initializer: - - def __init__(self, db_connection: sqlite3.Connection) -> None: - - Initializes the adjustment builder. +from typing import Union - Sets the database_connection to the passed connection object. Sets all - other attributes to None. +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.adjustments import Adjustment - Instance Methods: +class AdjustmentBuilder(DataItemBuilder): + """Assigns attributes and returns Adjustment Objects. - build(): Returns the Adjustment object. Assigns the Adjustment's - properties. + This class inherits methods and attributes from the DataItemBuilder. + Review the documentation for more information. - set_adjustment_date(): Sets the date which the adjustment occurred. + Methods: - set_event_code(): Sets the unique event_code of the adjustment. + build: Validates attributes and returns the Adjustment object. - set_medication_code(): Sets the medication_code of the medication - which was adjusted. + set_adjustment_date: Sets the adjustment date to the passed integer. - set_adjustment_amount(): Sets the amount of medication changed in this - adjustment. + set_event_code: Sets the event code attribute to the passed string. - set_reference_id(): Sets the identifier of the reference material - which contains additional information regarding the adjustment. + set_medication_code: Sets the medication code to the passed string. - set_modified_by(): Sets the identifier of the user who created the - adjustment. + set_adjustment_amount: Sets the adjustment amount to the passed float. - assign_all_attributes(): Assigns all attributes of the adjustment. - - assign_adjustment_id(): Sets the adjustment's id number. Should not be - called by the user. - - assign_amount_in_mcg(): Manually sets (or calculates) the - amount_in_mcg attribute. - - return_event_operator(): Returns the operator from the events table. - - assign_created_date(): Manually sets the created_date attribute. - - assign_modified_date(): Manually sets the modified_date attribute. - - - assign_reporting_period(): Manually sets (or calculates) the Reporting - Period ID. - - Exceptions: - - ValueError: This exception is thrown when a negative value is set as - the adjustment_amount. + set_reference_id: Sets the reference id attribute to the passed string. + set_reporting_period_id: Sets the reporting period id attribute to the + passed integer. """ - def __init__(self, db_connection: sqlite3.Connection) -> None: - """Initializes the adjustment builder. - - Sets the database_connection to the passed connection object. Sets all - other attributes to None. - """ - self.database_connection = db_connection - self.adjustment_id = None - self.adjustment_date = None - self.event_code = None - self.medication_code = None - self.amount_in_preferred_unit = None - self.amount_in_mcg = None - self.reporting_period_id = None - self.reference_id = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "inventory.Adjustment": - """Returns the Adjustment object. Assigns the Adjustment's attributes. - - This is the last method to be called as part of the building process. - It will return the Adjustment object with all of its attributes set. - - Returns: - inventory.Adjustment: The inventory adjustment object. - """ - if self.amount_in_mcg == None: - self.assign_amount_in_mcg() - - if self.reporting_period_id == None: - self.assign_reporting_period(self.database_connection) - - return inventory.Adjustment(self) - - def set_adjustment_date(self, adjustment_date: str) -> None: - """Sets the date which the adjustment occurred. - - Args: - adjustment_date (str): The date when the adjustment occurred. - Formatted as YYYY-MM-DD HH:MM:SS. - """ - self.adjustment_date = database.return_datetime(adjustment_date) - - def set_event_code(self, event_code: str) -> None: - """Sets the unique event_code of the adjustment. - - The event_code is the unique identifier for the event which occurred. - Valid events are ones which are saved in the event_types table of the - database. Using event_codes which are not listed in that table will - throw an error when trying to save the item to the inventory table. - - Args: - event_code (str): Unique identifier for the event from the - event_types table. - """ - self.event_code = event_code - - def set_medication_code(self, medication_code: str) -> None: - """Sets the medication_code of the medication which was adjusted. - - The medication_code is the unique identifier for the medication which - was adjusted. Valid medications are stored in the medications table. - Using invalid medication codes with throw and error when saving the - adjustment to the database. - - Args: - medication_code (str): Unique identifier of the medication from - the medications table. - """ - self.medication_code = medication_code - - def set_adjustment_amount(self, amount: float) -> None: - """Sets the amount of medication changed in this adjustment. - - Specify the amount using the preferred unit and as a positive number. - - Args: - adjustment_amount (float): The amount of medication that was - changed. Should be denoted in the preferred unit of measurement - and always as a positive number. + _dataitem = Adjustment( + table="inventory", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + adjustment_date=None, + event_code=None, + medication_code=None, + amount=None, + reference_id=None, + reporting_period_id=None, + ) + + def _reset(self) -> None: + """Prepares the builder to create a new Adjustment.""" + self._dataitem = Adjustment( + table="inventory", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + adjustment_date=None, + event_code=None, + medication_code=None, + amount=None, + reference_id=None, + reporting_period_id=None, + ) - Raises: - ValueError: Raised if a negative value is passed as amount. - """ - if amount < 0: - raise ValueError("Adjustment Amount should always be a positive value.") + def build(self) -> Adjustment: + """Validates attributes and returns the Adjustment object.""" + self._dataitem.created_date = self._service_provider.datetime.validate( + self._dataitem.created_date + ) + self._dataitem.modified_date = self._service_provider.datetime.validate( + self._dataitem.modified_date + ) + self._dataitem.adjustment_date = self._service_provider.datetime.validate( + self._dataitem.adjustment_date + ) - self.amount_in_preferred_unit = amount + adjustment = self._dataitem + self._reset() + return adjustment - def set_reference_id(self, reference_id: str) -> None: - """Sets the reference ID for the adjustment. + def set_adjustment_date(self, date: Union[int, str]) -> "AdjustmentBuilder": + """Sets the adjustment date to the passed value. Args: - reference_id (str): The unique identifier of the origin of the - adjustment event. + adjustment_date (int): Unix timestamp of when the adjustment + occurred. """ - self.reference_id = reference_id + self._dataitem.adjustment_date = date + return self - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Adjustment. + def set_event_code(self, event_code: str) -> "AdjustmentBuilder": + """Sets the event code attribute to the passed string. Args: - modified_by (str): The identifier of the user who created the - adjustment. + event_code (str): Unique code of the event which occurred. Must + match an event stored in the events table. """ - self.modified_by = modified_by - - def assign_all_attributes(self, attributes: dict) -> None: - """Assigns all attributes of the Adjustment. + self._dataitem.event_code = event_code + return self - This method is intended to be called when loading an Adjustment from - the database. + def set_medication_code(self, medication_code: str) -> "AdjustmentBuilder": + """Sets the medication code to the passed string. Args: - attributes (dict): The attributes of the adjustment. Dictionary - keys are formatted as the adjustment property names. + medication_code (str): Unique code of the medication being + adjusted. Must match a medication stored in the medications + table. """ - self.assign_adjustment_id(attributes["adjustment_id"]) - self.adjustment_date = attributes["adjustment_date"] - self.set_event_code(attributes["event_code"]) - self.set_medication_code(attributes["medication_code"]) - self.assign_amount_in_mcg(attributes["amount_in_mcg"]) - self.set_reference_id(attributes["reference_id"]) - self.assign_reporting_period( - self.database_connection, attributes["reporting_period_id"] - ) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) + self._dataitem.medication_code = medication_code + return self - def assign_adjustment_id(self, adjustment_id: int = None) -> None: - """Sets the adjustment's id number. Should not be called by the user. - - This method will set the adjustment's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the adjustment is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. + def set_adjustment_amount(self, adjustment_amount: float) -> "AdjustmentBuilder": + """Sets the adjustment amount to the passed float. Args: - adjustment_id (int): The adjustment's unique id. Defaults to None. + adjustment_amount (float): The amount of medication being adjusted. """ - self.adjustment_id = adjustment_id - - def assign_amount_in_mcg(self, amount: float = None) -> None: - """Manually sets (or calculates) the amount_in_mcg attribute. - - If an amount is passed it will be set directly. Otherwise the - correct amount will be assigned by converting the - adjustment_amount from the medication's preferred unit to micrograms - and multiplying it against the event's operator. + self._dataitem.amount = adjustment_amount + return self - Note: This method is not intended to be called when building an - Adjustment. + def set_reference_id(self, reference_id: str) -> "AdjustmentBuilder": + """Sets the reference id attribute to the passed string. Args: - db_connection (sqlite3.Connection): The connection to the - database. - - amount (float): The amount of medication changed in micrograms. - Optional. Defaults to None. - """ - if amount: - self.amount_in_mcg = amount - return - - preferred_unit = medications.return_preferred_unit( - self.medication_code, db_connection=self.database_connection - ) - - converted_amount = unit_converter.UnitConverter.to_mcg( - self.amount_in_preferred_unit, preferred_unit - ) - - operator = self.return_event_operator() - - self.amount_in_mcg = converted_amount * operator - - def return_event_operator(self) -> int: - """Returns the operator from the events table. + reference_id (str): Identifier of the document containing + additional information regarding the adjustment. Returns: - - int: The operator of the event.""" - sql_query = ( - f"""SELECT operator FROM events WHERE event_code ='{self.event_code}'""" - ) - - return self.database_connection.return_data(sql_query)[0][0] - - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. - - Note: This method is not intended to be called when building an - Adjustment. - - Args: - created_date (str): The date the adjustment object was created. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. + self: The instance of the builder. """ - self.created_date = database.return_datetime(created_date) + self._dataitem.reference_id = reference_id + return self - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. - - Note: This method is not intended to be called when building an - Adjustment. + def set_reporting_period_id(self, reporting_period_id: int) -> "AdjustmentBuilder": + """Sets the reporting period id attribute to the passed integer. Args: - modified_date (str): The date the adjustment was last modified. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.modified_date = database.return_datetime(modified_date) - - def assign_reporting_period( - self, db_connection: sqlite3.Connection, reporting_period: int = None - ) -> None: - """Manually sets (or calculates) the Reporting Period ID. + reporting_period_id (str): Identifier of the document containing + additional information regarding the adjustment. - If a reporting_period is passed it will be assigned directly. - Otherwise the correct reporting period will be assigned based on the - adjustment_date. - - Note: This method is not intended to be called when building an - Adjustment. - - Args: - db_connection (sqlite3.Connection): The connection to the - database. - - reporting_period (int): The numeric identifier of the reporting - period which the adjustment fits in. Optional. Defaults to - None. + Returns: + self: The instance of the builder. """ - if reporting_period: - self.reporting_period_id = reporting_period - return - - _, periods = reporting_periods.return_periods(db_connection) - - for period in periods: - if period[1] <= self.adjustment_date and self.adjustment_date <= period[2]: - self.reporting_period_id = period[0] - return - else: - self.reporting_period_id = None + self._dataitem.reporting_period_id = reporting_period_id + return self diff --git a/narcotics_tracker/builders/adjustment_builder_template.py b/narcotics_tracker/builders/adjustment_builder_template.py deleted file mode 100644 index 4f64649..0000000 --- a/narcotics_tracker/builders/adjustment_builder_template.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Contains the template for the Adjustment Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Reporting Period Builder module for more information. - -Classes: - Adjustment: Defines the interface for the Adjustment Builder. -""" -from abc import ABC, abstractmethod - - -class Adjustment(ABC): - """Defines the interface for the Adjustment Builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_adjustment_id(self) -> None: - pass - - @abstractmethod - def set_adjustment_date(self) -> None: - pass - - @abstractmethod - def set_event_code(self) -> None: - pass - - @abstractmethod - def set_medication_code(self) -> None: - pass - - @abstractmethod - def set_adjustment_amount(self) -> None: - pass - - @abstractmethod - def set_reference_id(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/builders/container_builder.py b/narcotics_tracker/builders/container_builder.py deleted file mode 100644 index 93cac3f..0000000 --- a/narcotics_tracker/builders/container_builder.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Contains the concrete builder for the Container class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. - -Classes: - - ContainerBuilder: Builds and returns Container objects. -""" -from narcotics_tracker import database, containers -from narcotics_tracker.builders import container_builder_template - - -class ContainerBuilder(container_builder_template.Container): - """Builds and returns Container objects. - - There are two types of methods: 'set' methods can be called to manually - set attributes for the object; 'assign' methods perform calculations and - are used as part of the build method to assign other attributes like the - created date, or unit conversions. - - Look at the Container Class documentation in the Containers Module for - more information on how to use the Container objects. - - How To Use: - - 1. Initialize the builder by assigning it to a variable and passing a - database connection: - - `cont_builder = container_builder.ContainerBuilder(database_connection)` - - 2. Call the following methods and pass the required values: - - `set_container_code()`; `set_container_name()`; and - `set_modified_by()`; - - 3. Call the `build()` method to return a Container object. - - Initializer: - - def __init__(self) -> None: - '''Initializes container builder. Sets all attributes to None.''' - self.container_id = None - self.container_code = None - self.container_name = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - Instance Methods: - - build(): Returns the Container object. Assigns the Container's - attributes. - - set_container_code(): Sets the unique code for the Container. - - set_container_name(): Sets the Container's name. - - set_modified_by(): Sets the identifier of the user who modified the - Container. - - assign_all_attributes(): Assigns all attributes of the Container. - - assign_container_id(): Manually sets the Container's id. Should not be - called by the user. - - assign_created_date(); Manually sets the created_date attribute. - - assign_modified_date(): Manually sets the modified_date attribute. - """ - - def __init__(self) -> None: - """Initializes container builder. Sets all attributes to None.""" - self.container_id = None - self.container_code = None - self.container_name = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "containers.Container": - """Returns the Container object. Assigns the Container's attributes. - - This is the last method to be called as part of the building process. - It will return the Container object with all of its attributes set. - - Returns: - containers.containerType: The Container Object. - """ - - return containers.Container(self) - - def set_container_code(self, container_code: str) -> None: - """Sets the unique code for the Container. - - The container code is the unique identifier used to find the Container - in the database. - - Args: - container_code (str): The unique identifier for the container. It is - recommended that the container's code should be the common lowercase - abbreviation for the dosage container. - """ - self.container_code = container_code - - def set_container_name(self, container_name: str) -> None: - """Sets the Container's name. - - Args: - container_name (str): The proper name for the dosage container. - """ - self.container_name = container_name - - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Container. - - This method will set the Container's modified by attribute. - - Args: - modified_by (str): Identifier of the user who modified the - container. - """ - self.modified_by = modified_by - - def assign_all_attributes(self, attributes: dict) -> None: - """Assigns all attributes of the Container. - - This method is intended to be called when loading a Container from - the database. - - Args: - attributes (dict): The attributes of the Container. - Dictionary keys are formatted as the Container attribute - names. - """ - self.assign_container_id(attributes["container_id"]) - self.set_container_code(attributes["container_code"]) - self.set_container_name(attributes["container_name"]) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) - - def assign_container_id(self, container_id: int) -> None: - """Manually sets the Container's id. Should not be called by the user. - - This method will set the container's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the Container is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. - - Args: - container_id (int): The Container's numeric id. - """ - self.container_id = container_id - - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. - - Note: This method is not intended to be called when building a - Container. - - Args: - created_date (str): The date the container object was created. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.created_date = database.return_datetime(created_date) - - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. - - Note: This method is not intended to be called when building a - Container. - - Args: - modified_date (str): The date the Container was last modified. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.modified_date = database.return_datetime(modified_date) diff --git a/narcotics_tracker/builders/container_builder_template.py b/narcotics_tracker/builders/container_builder_template.py deleted file mode 100644 index b397e21..0000000 --- a/narcotics_tracker/builders/container_builder_template.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Contains the template for the Container Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Reporting Period Builder module for more information. - -Classes: - Container: Defines the interface for the Container Builder. -""" -from abc import ABC, abstractmethod - - -class Container(ABC): - """Defines the interface for the Container builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_container_id(self) -> None: - pass - - @abstractmethod - def set_container_code(self) -> None: - pass - - @abstractmethod - def set_container_name(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def assign_all_attributes(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/builders/dataitem_builder.py b/narcotics_tracker/builders/dataitem_builder.py new file mode 100644 index 0000000..dca2478 --- /dev/null +++ b/narcotics_tracker/builders/dataitem_builder.py @@ -0,0 +1,82 @@ +"""Defines the builder for generic DataItems. Only meant to be inherited from. + +Classes: + + DataItemBuilder: Builds a generic DataItem. Intended to be inherited by + other builders. + +""" +from typing import Union + +from narcotics_tracker.builders.interfaces.builder import BuilderInterface +from narcotics_tracker.services.service_manager import ServiceManager + + +class DataItemBuilder(BuilderInterface): + """Builds a generic DataItem. Intended to be inherited by other builders. + + This class is meant to be inherited by other builders which create various + data items. + + Methods: + __init__: Calls the _reset method. + set_table: Sets the table attribute. + set_id: Sets the id attribute to None, unless overridden. + set_created_date: Sets the attribute to the current datetime, unless + overridden. + set_modified_date: Sets the attribute to the current datetime, unless + overridden. + set_modified_by: Sets the modified by attribute to the passed string. + """ + + _service_provider = ServiceManager() + + def __init__(self) -> None: + """Calls the _reset method.""" + self._reset() + + def set_table(self, table_name: str) -> BuilderInterface: + """Sets the table attribute.""" + self._dataitem.table = table_name + + return self + + def set_id(self, id_number: int = None) -> BuilderInterface: + """Sets the id attribute to None, unless overridden.""" + self._dataitem.id = id_number + return self + + def set_created_date(self, date: Union[int, str] = None) -> BuilderInterface: + """Sets the attribute to the current datetime, unless overridden. + + Args: + date (int, str, optional): Unix timestamp, or formatted date time + string (MM-DD-YYYY HH:MM:SS). If None, will use the current + datetime. + """ + self._dataitem.created_date = date + return self + + def set_modified_date(self, date: Union[int, str] = None) -> BuilderInterface: + """Sets the attribute to the current datetime, unless overridden. + + Args: + date (int, str, optional): Unix timestamp, or formatted date time + string (MM-DD-YYYY HH:MM:SS). If None, will use the current + datetime. + """ + self._dataitem.modified_date = date + return self + + def set_modified_by(self, modified_by: str) -> BuilderInterface: + """Sets the modified by attribute to the passed string.""" + self._dataitem.modified_by = modified_by + return self + + def build(self) -> None: + """Returns the DataItem object.""" + raise NotImplementedError + + def _reset(self) -> None: + """Sets all attributes to default.""" + raise NotImplementedError diff --git a/narcotics_tracker/builders/event_builder.py b/narcotics_tracker/builders/event_builder.py index 840591c..af9d189 100644 --- a/narcotics_tracker/builders/event_builder.py +++ b/narcotics_tracker/builders/event_builder.py @@ -1,200 +1,108 @@ -"""Contains the concrete builder for the Event class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. +"""Handles the defining and building of Event Objects. Classes: - EventBuilder: Builds and returns Event objects. + EventBuilder: Assigns attributes and returns Event Objects. """ -from narcotics_tracker import database, events -from narcotics_tracker.builders import event_builder_template - - -class EventBuilder(event_builder_template.Event): - """Builds and returns Event objects. - - There are two types of methods: 'set' methods can be called to manually - set attributes for the object; 'assign' methods perform calculations and - are used as part of the build method to assign other attributes like the - created date, or unit conversions. - - Look at the Event Class documentation in the Events Module for - more information on how to use the Event objects. - - How To Use: - - 1. Initialize the builder by assigning it to a variable: - - `e_builder = event_builder.EventBuilder()` - - 3. Call the following methods and pass the required values: - - `set_event_code()`; `set_event_name()`; `set_event_description()`; - - `set_operator()`; and `set_modified_by()`; - - 4. Call the `build()` method to return an Event object. - - Initializer: - def __init__(self) -> None: - '''Initializes the event_type builder. Sets all attributes to None.''' - Instance Methods: +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.events import Event - build: Returns the Event object. Assigns the Event's attributes. - set_event_code: Sets the Event's code. +class EventBuilder(DataItemBuilder): + """Assigns attributes and returns Event Objects. - set_event_name: Sets the Event's name. + This class inherits methods and attributes from the DataItemBuilder. + Review the documentation for more information. - set_description: Sets the Event's description. + Methods: - set_operator: Sets the Event's operator. + build: Validates attributes and returns the Event Object. - set_modified_by: ets the identifier of the user who modified the - Event. + set_event_code: Sets the event code attribute to the passed string. - assign_all_attributes: Assigns all attributes of the Event. + set_event_name: Sets the event name attribute to the passed string. - assign_event_id: Manually sets the Container's id. Should not be - called by the user. + set_description: Sets the event description to the passed string. - assign_created_date: Manually sets the created_date attribute. - - assign_modified_date: Manually sets the modified_date attribute. + set_modifier: Sets the modifier attribute to the passed integer. """ - def __init__(self) -> None: - """Initializes the event_type builder. Sets all attributes to None.""" - self.event_id = None - self.event_code = None - self.event_name = None - self.description = None - self.operator = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "events.Event": - """Returns the Event object. Assigns the Event's attributes. - - This is the last method to be called as part of the building process. - It will return the Event object with all of its attributes set. - - - Returns: - event_types.Event: The event_types Event object. - """ - - return events.Event(self) - - def set_event_code(self, event_code: str) -> None: - """Sets the Event's code. - - This method will set the Event's code which is used to the Event in - the database. - - Args: - event_code (str): The Event's unique id. - """ - self.event_code = event_code - - def set_event_name(self, event_name: str) -> None: - """Sets the Event's name. - - This method will set the Event's name. - - Args: - event_name (str): The Event's name - """ - self.event_name = event_name - - def set_description(self, description: str) -> None: - """Sets the Event's description. - - This method will set the Event's description. - - Args: - description (str): Description of the Event. - """ - self.description = description - - def set_operator(self, operator: int) -> None: - """Sets the Event's operator. - - This method will set the Event's operator used to determine if the - Event will Add or Remove medication from the inventory. - - Args: - operator (int): The Event's operator (-1 or +1) + _dataitem = Event( + table="events", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + event_code=None, + event_name=None, + description=None, + modifier=None, + ) + + def _reset(self) -> None: + """Prepares the builder to create a new Event.""" + self._dataitem = Event( + table="events", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + event_code=None, + event_name=None, + description=None, + modifier=None, + ) + + def build(self) -> Event: + """Validates attributes and returns the Event Object.""" + self._dataitem.created_date = self._service_provider.datetime.validate( + self._dataitem.created_date + ) + self._dataitem.modified_date = self._service_provider.datetime.validate( + self._dataitem.modified_date + ) + + event = self._dataitem + self._reset() + return event + + def set_event_code(self, event_code: str) -> "EventBuilder": + """Sets the event code attribute to the passed string. + + event_code (str): Unique code of the event which occurred. Must match + an event stored in the events table. """ - self.operator = operator - - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Event. + self._dataitem.event_code = event_code + return self - This method will set the Event's modified by attribute. + def set_event_name(self, event_name: str) -> "EventBuilder": + """Sets the event name attribute to the passed string. - Args: - modified_by (str): Identifier of the user who modified the Event. + event_name (str): Name of the event. """ - self.modified_by = modified_by - - def assign_all_attributes(self, attributes: dict) -> None: - """Assigns all attributes of the Event. + self._dataitem.event_name = event_name + return self - This method is intended to be called when loading an Event from the - database. - - Args: - attributes (dict): The attributes of the Event. Dictionary keys - are formatted as the Event attribute names. - """ - self.assign_event_id(attributes["event_id"]) - self.set_event_code(attributes["event_code"]) - self.set_event_name(attributes["event_name"]) - self.set_description(attributes["description"]) - self.set_operator(attributes["operator"]) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) - - def assign_event_id(self, event_id: int) -> None: - """Manually sets the Event's id. Should not be called by the user. - - This method will set the Event's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the Event is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. + def set_description(self, description: str) -> "EventBuilder": + """Sets the event description to the passed string. Args: - event_id (int): The Event's numeric id. + description (str): Description of the event. """ - self.event_id = event_id + self._dataitem.description = description + return self - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. - - Note: This method is not intended to be called when building an Event. + def set_modifier(self, modifier: int) -> "EventBuilder": + """Sets the modifier attribute to the passed integer. Args: - created_date (str): The date the Event object was created. Must be - in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.created_date = database.return_datetime(created_date) - - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. + modifier (int): (+1 / -1) Integer which determines if the event + adds or removes amounts from the inventory. - Note: This method is not intended to be called when building an Event. - - Args: - modified_date (str): The date the Event was last modified. Must be - in the format 'YYYY-MM-DD HH:MM:SS'. + Returns: + self: The instance of the builder. """ - self.modified_date = database.return_datetime(modified_date) + self._dataitem.modifier = modifier + return self diff --git a/narcotics_tracker/builders/event_builder_template.py b/narcotics_tracker/builders/event_builder_template.py deleted file mode 100644 index 7294fbf..0000000 --- a/narcotics_tracker/builders/event_builder_template.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Contains the template for the Event Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Reporting Period Builder module for more information. - -Classes: - Event: Defines the interface for the Event Builder. -""" -from abc import ABC, abstractmethod - - -class Event(ABC): - """Defines the interface for the Event Builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_event_id(self) -> None: - pass - - @abstractmethod - def set_event_code(self) -> None: - pass - - @abstractmethod - def set_event_name(self) -> None: - pass - - @abstractmethod - def set_description(self) -> None: - pass - - @abstractmethod - def set_operator(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/builders/interfaces/__init__.py b/narcotics_tracker/builders/interfaces/__init__.py new file mode 100644 index 0000000..049e3f3 --- /dev/null +++ b/narcotics_tracker/builders/interfaces/__init__.py @@ -0,0 +1,6 @@ +"""Contains the interfaces for the Builders Package + +Modules: + + builder: Contains the interface for concrete DataItem builders. +""" diff --git a/narcotics_tracker/builders/interfaces/builder.py b/narcotics_tracker/builders/interfaces/builder.py new file mode 100644 index 0000000..26a6f60 --- /dev/null +++ b/narcotics_tracker/builders/interfaces/builder.py @@ -0,0 +1,26 @@ +"""Contains the interface for concrete DataItem builders. + +Classes: + BuilderInterface: Defines the protocol for concrete DataItem builders. +""" + +from typing import TYPE_CHECKING, Protocol + +if TYPE_CHECKING: + from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +class BuilderInterface(Protocol): + """Defines the protocol for concrete DataItem builders. + + Abstract Methods: + build: Should return the constructed DataItem Object. + + _reset: Should remove all attributes values. + """ + + def build(self) -> "DataItem": + """Returns the constructed DataItem Object.""" + + def _reset(self) -> None: + """Removes all attributes values from the builder.""" diff --git a/narcotics_tracker/builders/medication_builder.py b/narcotics_tracker/builders/medication_builder.py index 4d281b1..515b834 100644 --- a/narcotics_tracker/builders/medication_builder.py +++ b/narcotics_tracker/builders/medication_builder.py @@ -1,295 +1,190 @@ -"""Contains the concrete builder for the Medication class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. +"""Handles the defining and building of Medication Objects. Classes: - MedicationBuilder: Builds and returns Medication objects. + MedicationBuilder: Assigns attributes and returns Medication Objects. """ -from narcotics_tracker import database, medications -from narcotics_tracker.builders import medication_builder_template -from narcotics_tracker.utils import unit_converter - - -class MedicationBuilder(medication_builder_template.Medication): - """Builds and returns medication objects. - - There are two types of methods: 'set' methods can be called to manually - set attributes for the object; 'assign' methods perform calculations and - are used as part of the build method to assign other attributes like the - created date, or unit conversions. - - Look at the Medication Class documentation in the Medications Module for - more information on how to use the Medication objects. - - How To Use: - - 1. Initialize the builder by assigning it to a variable: - - `e_builder = medication_builder.MedicationBuilder()` - - 2. Call the following methods and pass the required values: - - `set_medication_name()`; `set_medication_code()`; `set_container()`; - - `set_fill_amount()`; `set_dose_and_unit()`; `set_medication_status()` - - and `set_modified_by()`; - - 3. Call the `build()` method to return an Medication object. - - Initializer: - - def __init__(self) -> None: - - Initializes the medication builder. Sets all attributes to None. - - Instance Methods: +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.medications import Medication - build: Returns the medication object. Assigns the medication's - attributes. - set_medication_name: Sets the medication's name. +class MedicationBuilder(DataItemBuilder): + """Assigns attributes and returns Medication Objects. - set_medication_code: Sets the unique code of the medication. + This class inherits methods and attributes from the DataItemBuilder. + Review the documentation for more information. - set_container: Sets the container the medication comes in. + Methods: - set_fill_amount: Sets the medication's fill amount. + build: Validates attributes and returns the Medication Object. - set_dose_and_unit: Sets the medication's dose and its preferred unit. + set_medication_code: Sets the medication code to the passed string. - set_medication_status: Sets the medication's status. + set_medication_name: Sets the medication name attribute to the passed + string. - set_modified_by: Sets the identifier of the user who modified the - Medication. + set_fill_amount: Sets the fill amount to the passed integer. - calculate_concentration: Calculates the concentration of the - medication. + set_medication_amount: Sets the amount to the passed integer. - assign_all_attributes: Assigns all attributes of the Medication. + set_preferred_unit: Sets the preferred unit to the passed string. - assign_medication_id: Manually sets the Medication's id. Should not be - called by the user. - - assign_concentration: Manually sets the concentration. Should not be - called by the user. - - assign_created_date: Manually sets the created_date attribute. - - assign_modified_date: Manually sets the modified_date attribute. + set_concentration: Sets the concentration to the passed value, or None. + set_status: Sets the status attribute to the passed string. """ - def __init__(self) -> None: - """Initializes the medication builder. Sets all attributes to None.""" - self.medication_id = None - self.medication_code = None - self.name = None - self.container_type = None - self.fill_amount = None - self.dose = None - self.preferred_unit = None - self.concentration = None - self.status = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "medications.Medication": - """Returns the medication object. Assigns the medication's attributes. - - This is the last method to be called as part of the building process. - It will return the medication object with all of its attributes set. - The concentration is calculated using the calculate_concentration - method. - - Returns: - medication.Medication: The medication object. - """ - self.calculate_concentration() - - return medications.Medication(self) - - def set_medication_name(self, name: str) -> None: - """Sets the medication's name. + _dataitem = Medication( + table="medications", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + medication_code=None, + medication_name=None, + fill_amount=None, + medication_amount=None, + preferred_unit=None, + concentration=None, + status=None, + ) + + def _reset(self) -> None: + """Prepares the builder to create a new Medication.""" + self._dataitem = Medication( + table="medications", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + medication_code=None, + medication_name=None, + fill_amount=None, + medication_amount=None, + preferred_unit=None, + concentration=None, + status=None, + ) + + def build(self) -> Medication: + """Validates attributes and returns the Medication Object.""" + self._dataitem.created_date = self._service_provider.datetime.validate( + self._dataitem.created_date + ) + self._dataitem.modified_date = self._service_provider.datetime.validate( + self._dataitem.modified_date + ) + + if self._concentration_is_none: + self._dataitem.concentration = self._calculate_concentration() + + self._dataitem.medication_amount = self._convert_medication_amount() + + medication = self._dataitem + self._reset() + + return medication + + def _concentration_is_none(self) -> bool: + """Returns true if the concentration is None, otherwise returns false.""" + if self._dataitem.concentration is not None: + return False + else: + return True + + def _calculate_concentration(self) -> float: + """Calculates and returns the medication's concentration.""" + return self._dataitem.medication_amount / self._dataitem.fill_amount + + def _convert_medication_amount(self) -> int: + """Converts and returns the medication amount in the standard unit.""" + unit_converter = self._service_provider.conversion + converted_amount = unit_converter.to_standard( + self._dataitem.medication_amount, self._dataitem.preferred_unit + ) + + return converted_amount + + def set_medication_code(self, medication_code: str) -> "MedicationBuilder": + """Sets the medication code to the passed string. Args: - name (str): The medication's name. - """ - self.name = name - - def set_medication_code(self, code: str) -> None: - """Sets the unique code of the medication. - - The medications unique code is used to identify the medication within - the database and the Narcotics Tracker. This code is set by the user - as an easy reference to the medication. Medications without a code are - not retrievable from the database. - - Args: - code (str): Identifier for this specific medication. - """ - self.medication_code = code - - def set_container(self, container_type: str) -> None: - """Sets the container the medication comes in. - - Containers are defined in the containers table of the database as well - as the Containers Module and class. + medication_code (str): Unique code of the medication. - The Narcotics Tracker comes with the following pre-defined containers - for medications: - Vial - Container Code: 'vial' - Pre-filled Syringe - Container Code: 'pfs' - Pre-mixed Bag - Container Code: 'pmb' - - - Args: - container_type (str): The type of container the - medication comes in. Defaults to None. + Returns: + self: The MedicationBuilder instance. """ - self.container_type = container_type + self._dataitem.medication_code = medication_code + return self - def set_fill_amount(self, fill_amount: float) -> None: - """Sets the medication's fill amount. + def set_medication_name(self, medication_name: str) -> "MedicationBuilder": + """Sets the medication name attribute to the passed string. - Each comes dissolved within a liquid (solvent). The fill amount is the - measurement of that liquid in the container measured in milliliters - (ml). + medication_name (str): Name of the medication. - Args: - fill_amount (float): The amount of medication in the container. - Defaults to None. + Returns: + self: The MedicationBuilder instance. """ - self.fill_amount = fill_amount - - def set_dose_and_unit(self, dose: float, preferred_unit: str) -> None: - """Sets the medication's dose and its preferred unit. - - The dose is converted into micrograms which is how the Narcotics - Tracker stores all medication amounts. The preferred unit is saved so - the amounts can be converted back when being shown to the user. + self._dataitem.medication_name = medication_name + return self - Look at the Unit Converter Module for more information on converting - between units. + def set_fill_amount(self, fill_amount: int) -> "MedicationBuilder": + """Sets the fill amount to the passed integer. Args: - dose (float): The amount of medication dissolved in the container. + fill_amount (int): The amount of liquid in the medication + container, in milliliters. - preferred_unit (str): The unit the medication is commonly measured - in. - """ - self.dose = unit_converter.UnitConverter.to_mcg(dose, preferred_unit) - self.unit = preferred_unit - - def set_medication_status(self, status: str) -> None: - """Sets the medication's status. - - Statuses determine when objects can and cannot be used. They are - defined in the statuses table of the database as well as the Statuses - module and class. - - The Narcotics Tracker comes with pre-defined statuses for medications: - ACTIVE - INACTIVE - - Args: - status (str): The status of the medication. + Returns: + self: The MedicationBuilder instance. """ - self.status = status - - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Medication. + self._dataitem.fill_amount = fill_amount + return self - This method will set the Medication's modified by attribute. + def set_medication_amount(self, medication_amount: int) -> "MedicationBuilder": + """Sets the amount to the passed integer. Args: - modified_by (str): Identifier of the user who modified the - Medication. - """ - self.modified_by = modified_by - - def calculate_concentration(self) -> None: - """Calculates the concentration of the medication. + medication_amount (int): The amount of medication suspended in the + liquid. - Will calculate the concentration of the medications as a decimal by - diving the dose by the fill amount. This is useful when converting - medication amounts into milliliters for reports. + Returns: + self: The MedicationBuilder instance. """ - self.concentration = self.dose / self.fill_amount - - def assign_all_attributes(self, attributes: dict) -> None: - """Assigns all attributes of the Medication. + self._dataitem.medication_amount = medication_amount + return self - This method is intended to be called when loading an Medication from - the database. + def set_preferred_unit(self, preferred_unit: str) -> "MedicationBuilder": + """Sets the preferred unit to the passed string. Args: - attributes (dict): The attributes of the Medication. Dictionary - keys are formatted as the Medication attribute names. - """ - self.assign_medication_id(attributes["medication_id"]) - self.set_medication_name(attributes["name"]) - self.set_medication_code(attributes["medication_code"]) - self.set_container(attributes["container_type"]) - self.set_fill_amount(attributes["fill_amount"]) - self.set_dose_and_unit(attributes["dose"], attributes["unit"]) - self.assign_concentration(attributes["concentration"]) - self.set_medication_status(attributes["status"]) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) - - def assign_medication_id(self, medication_id: int) -> None: - """Manually sets the Medication's id. Should not be called by the user. - - This method will set the Medication's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the Medication is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. + preferred_unit (str): Unit of measurement the medication is + measured in. Must match a unit_code in the units table. - Args: - medication_id (int): The Medication's numeric id. + Returns: + self: The MedicationBuilder instance. """ - self.medication_id = medication_id - - def assign_concentration(self, concentration: float) -> None: - """Manually sets the concentration. Should not be called by the user. + self._dataitem.preferred_unit = preferred_unit + return self - This method will set the Medications's concentration number. This - method is useful in setting the concentration when the Medications is - loaded from the database. It will override any id number that is - already set and may cause errors in the within the database table. - Users should not call this method. + def set_concentration(self, concentration: float = None) -> "MedicationBuilder": + """Sets the concentration to the passed value, or None.""" + if concentration: + self._dataitem.concentration = concentration + else: + concentration = None - Args: - concentration (float): The Medication's concentration. - """ - self.concentration = concentration + return self - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. - - Note: This method is not intended to be called when building an Medication. + def set_status(self, status: str) -> "MedicationBuilder": + """Sets the status attribute to the passed string. Args: - created_date (str): The date the Medication object was created. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.created_date = database.return_datetime(created_date) + status (str): Status of the medication. Must match a status_code + in the statuses table. - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. - - Note: This method is not intended to be called when building an Medication. - - Args: - modified_date (str): The date the Medication was last modified. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. + Returns: + self: The instance of the builder. """ - self.modified_date = database.return_datetime(modified_date) + self._dataitem.status = status + return self diff --git a/narcotics_tracker/builders/medication_builder_template.py b/narcotics_tracker/builders/medication_builder_template.py deleted file mode 100644 index a8ca9ad..0000000 --- a/narcotics_tracker/builders/medication_builder_template.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Contains the template for the Medication Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Reporting Period Builder module for more information. - -Classes: - Medication: Defines the interface for the Medication Builder. -""" -from abc import ABC, abstractmethod - - -class Medication(ABC): - """Defines the interface for the Medication Builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_medication_id(self) -> None: - pass - - @abstractmethod - def set_medication_name(self) -> None: - pass - - @abstractmethod - def set_medication_code(self) -> None: - pass - - @abstractmethod - def set_container(self) -> None: - pass - - @abstractmethod - def set_fill_amount(self) -> None: - pass - - @abstractmethod - def set_dose_and_unit(self) -> None: - pass - - @abstractmethod - def assign_concentration(self) -> None: - pass - - @abstractmethod - def set_medication_status(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/builders/reporting_period_builder.py b/narcotics_tracker/builders/reporting_period_builder.py index 9c1e52f..4857634 100644 --- a/narcotics_tracker/builders/reporting_period_builder.py +++ b/narcotics_tracker/builders/reporting_period_builder.py @@ -1,174 +1,111 @@ -"""Contains the concrete builder for the ReportingPeriod class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. +"""Handles the defining and building of Reporting Period Objects. Classes: - ReportingPeriodBuilder: Builds and returns ReportingPeriod objects. + ReportingPeriodBuilder: Assigns attributes and returns Reporting Period + Objects. """ -from narcotics_tracker import database, reporting_periods -from narcotics_tracker.builders import reporting_period_builder_template - - -class ReportingPeriodBuilder(reporting_period_builder_template.ReportingPeriod): - """Builds and returns Reporting Period objects. - - There are two types of methods: 'set' methods can be called to manually - set attributes for the object; 'assign' methods perform calculations and - are used as part of the build method to assign other attributes like the - created date, or unit conversions. - - Look at the ReportingPeriod Class documentation in the Reporting Periods Module for - more information on how to use the Reporting Period objects. - - How To Use: - - 1. Initialize the builder by assigning it to a variable: - - `p_builder = reporting_period_builder.ReportingPeriodBuilder()` - - 2. Call the following methods and pass the required values: - - `set_starting_date()`; `set_ending_date()`; and - - `set_modified_by()`; +from typing import Union - 3. Call the `build()` method to return an Reporting Period object. +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.reporting_periods import ReportingPeriod - Initializer: - def __init__(self) -> None: +class ReportingPeriodBuilder(DataItemBuilder): + """Assigns attributes and returns Reporting Period Objects. - Initializes the Reporting Period Builder. Sets all attributes to None. + This class inherits methods and attributes from the DataItemBuilder. + Review the documentation for more information. - Instance Methods: + Methods: - build: Returns the Reporting Period object and assigns attributes. + build: Validates attributes and returns the ReportingPeriod Object. - set_starting_date: Sets the ReportingPeriod's starting_date. + set_start_date: Sets the start date attribute to the passed integer. - set_ending_dated: Sets the ReportingPeriod's ending_date. + set_end_date: Sets the end date attribute to the passed integer. - set_modified_by: Sets the identifier of the user who modified the - Reporting Period. - - assign_all_attributes: Assigns all attributes of the Reporting Period. - - assign_reporting_period_id: Manually sets the Reporting Period's id. - Not be called by the user. - - assign_created_date: Manually sets the created_date attribute. - - assign_modified_date: Manually sets the modified_date attribute. + set_status: Sets the status attribute to the passed string. """ - def __init__(self) -> None: - """Initializes reporting Period builder. Sets all attributes to None.""" - self.period_id = None - self.starting_date = None - self.ending_date = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "reporting_periods.ReportingPeriodType": - """Returns the Reporting Period object and assigns attributes. - - This is the last method to be called as part of the building process. - It will return the ReportingPeriodType object with all of its - properties set. - - - Returns: - reporting_periods.ReportingPeriodType: The ReportingPeriod Object. - """ - - return reporting_periods.ReportingPeriod(self) - - def set_starting_date(self, starting_date: str) -> None: - """Sets the ReportingPeriod's starting_date. - - This method will set the ReportingPeriod's starting_date. + _dataitem = ReportingPeriod( + table="reporting_periods", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + start_date=None, + end_date=None, + status=None, + ) + + def _reset(self) -> None: + """Prepares the builder to create a new ReportingPeriod.""" + self._dataitem = ReportingPeriod( + table="reporting_periods", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + start_date=None, + end_date=None, + status=None, + ) + + def build(self) -> ReportingPeriod: + """Validates attributes and returns the ReportingPeriod Object.""" + self._dataitem.created_date = self._service_provider.datetime.validate( + self._dataitem.created_date + ) + self._dataitem.modified_date = self._service_provider.datetime.validate( + self._dataitem.modified_date + ) + self._dataitem.start_date = self._service_provider.datetime.validate( + self._dataitem.start_date + ) + self._dataitem.end_date = self._service_provider.datetime.validate( + self._dataitem.end_date + ) + + reporting_period = self._dataitem + self._reset() + return reporting_period + + def set_start_date(self, date: Union[int, str] = None) -> "ReportingPeriodBuilder": + """Sets the start date attribute to the passed value. Args: - starting_date (str): The date the reporting period started on. - Formatted as YYYY-MM-DD HH:MM:SS. - """ - self.starting_date = database.return_datetime(starting_date) + start_date (int): Unix timestamp of when the reporting period + started. - def set_ending_date(self, ending_date: str) -> None: - """Sets the ReportingPeriod's ending_date. - - This method will set the ReportingPeriod's ending_date. - - Args: - ending_date (str): The date teh reporting period ended on. - Formatted as YYYY-MM-DD HH:MM:SS. - """ - self.ending_date = database.return_datetime(ending_date) - - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Reporting Period. - - This method will set the Reporting Period's modified by attribute. - - Args: - modified_by (str): Identifier of the user who modified the - Reporting Period. + Returns: + self: The instance of the builder. """ - self.modified_by = modified_by + self._dataitem.start_date = date + return self - def assign_all_attributes(self, attributes: dict) -> None: - """Assigns all attributes of the Reporting Period. + def set_end_date(self, date: int = None) -> "ReportingPeriodBuilder": + """Sets the end date attribute to the passed integer. Args: - attributes (dict): The attributes of the ReportingPeriod. - Dictionary keys are formatted as the ReportingPeriod attribute - names. - """ - self.assign_period_id(attributes["period_id"]) - self.set_starting_date(attributes["starting_date"]) - self.set_ending_date(attributes["ending_date"]) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) - - def assign_period_id(self, period_id: int) -> None: - """Manually sets the Reporting Period's id. Not be called by the user. - - This method will set the Reporting Period's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the Reporting Period is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. + end_date (int): Unix timestamp of when the reporting period + ended. - Args: - period_id (int): The Reporting Period's numeric id. + Returns: + self: The instance of the builder. """ - self.period_id = period_id - - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. + self._dataitem.end_date = date + return self - Note: This method is not intended to be called when building an Medication. + def set_status(self, status: str) -> "ReportingPeriodBuilder": + """Sets the status attribute to the passed string. Args: - created_date (str): The date the Medication object was created. - Must be in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.created_date = database.return_datetime(created_date) - - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. - - Note: This method is not intended to be called when building a - Reporting Period. + status (str): Status of the reporting period. Must match a + status_code in the statuses table. - Args: - modified_date (str): The date the Reporting Period was last - modified. Must be in the format 'YYYY-MM-DD HH:MM:SS'. + Returns: + self: The instance of the builder. """ - self.modified_date = database.return_datetime(modified_date) + self._dataitem.status = status + return self diff --git a/narcotics_tracker/builders/reporting_period_builder_template.py b/narcotics_tracker/builders/reporting_period_builder_template.py deleted file mode 100644 index 82737c6..0000000 --- a/narcotics_tracker/builders/reporting_period_builder_template.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Contains the template for the Reporting Period Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Reporting Period Builder module for more information. - -Classes: - ReportingPeriod: Defines the interface for the Reporting Period Builder. -""" -from abc import ABC, abstractmethod - - -class ReportingPeriod(ABC): - """Defines the interface for the Reporting Period Builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_period_id(self) -> None: - pass - - @abstractmethod - def set_starting_date(self) -> None: - pass - - @abstractmethod - def set_ending_date(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/builders/status_builder.py b/narcotics_tracker/builders/status_builder.py index 1d16099..5144fbe 100644 --- a/narcotics_tracker/builders/status_builder.py +++ b/narcotics_tracker/builders/status_builder.py @@ -1,188 +1,99 @@ -"""Contains the concrete builder for the Status class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. +"""Handles the defining and building of Status Objects. Classes: - StatusBuilder: Builds and returns Status objects. + StatusBuilder: Assigns attributes and returns Status Objects. """ -from narcotics_tracker import database, statuses -from narcotics_tracker.builders import status_builder_template - - -class StatusBuilder(status_builder_template.Status): - """Builds and returns Status objects. - - There are two types of methods: 'set' methods can be called to manually - set attributes for the object; 'assign' methods perform calculations and - are used as part of the build method to assign other attributes like the - created date, or unit conversions. - - Look at the Status Class documentation in the Statuses Module for - more information on how to use the Status objects. - - How To Use: - - 1. Initialize the builder by assigning it to a variable: - - `stat_builder = status_builder.StatusBuilder()` - - 2. Call the following methods and pass the required values: - - `set_status_code()`; `set_status_name()`; - - `set_description()`; and `set_modified_by()`; - - 3. Call the `build()` method to return an Status object. - - Initializer: +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.statuses import Status - def __init__(self) -> None: - Initializes status builder. Sets all attributes to None. +class StatusBuilder(DataItemBuilder): + """Assigns attributes and returns Status Objects. - Instance Methods: + This class inherits methods and attributes from the DataItemBuilder. + Review the documentation for more information. - build: Returns the Status object and assigns attributes. + Methods: - set_status_code: Sets the unique code of the Status. + build: Validates attributes and returns the Status object. - set_status_name: Sets the proper name of the Status. + set_status_code: Sets the status_code attribute to the passed string. - set_description: Sets the description of the Status. - - set_modified_by: Sets the identifier of the user who modified the - Status. - - assign_all_attributes: Sets all attributes of the status. - - assign_status_id: Manually sets the Status's id. Not be called by the - user. - - assign_created_date: Manually sets the created_date attribute. - - assign_modified_date: Manually sets the modified_date attribute. - - Exceptions: + set_status_name: Sets the status_name attribute to the passed string. + set_description: Sets the description attribute to the passed string. """ - def __init__(self) -> None: - """Initializes status builder. Sets all attributes to None.""" - self.status_id = None - self.status_code = None - self.status_name = None - self.description = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "statuses.status": - """Returns the Status object and assigns attributes. - - This is the last method to be called as part of the building process. - It will return the Status object with all of its - properties set. - - - Returns: - statuses.Status: The Status Object. - """ - - return statuses.Status(self) - - def set_status_code(self, status_code: str) -> None: - """Sets the unique code of the Status. - - The status code is the unique identifier for the status. It is used to - interact with the status in the database. The status code should be - the lower case abbreviation of the status. - - Args: - status_code (str): The unique identifier for the status. It is - recommended that the statuses' code should be the common lowercase - abbreviation for the status. - """ - self.status_code = status_code - - def set_status_name(self, status_name: str) -> None: - """Sets the proper name of the Status. + _dataitem = Status( + table="statuses", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + status_code=None, + status_name=None, + description=None, + ) + + def _reset(self) -> None: + """Prepares the builder to create a new Status.""" + self._dataitem = Status( + table="statuses", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + status_code=None, + status_name=None, + description=None, + ) + + def build(self) -> Status: + """Validates attributes and returns the Status object.""" + self._dataitem.created_date = self._service_provider.datetime.validate( + self._dataitem.created_date + ) + self._dataitem.modified_date = self._service_provider.datetime.validate( + self._dataitem.modified_date + ) + + status = self._dataitem + self._reset() + return status + + def set_status_code(self, status_code: str) -> "StatusBuilder": + """Sets the status_code attribute to the passed string. Args: - status_name (str): The proper name for the status. - """ - self.status_name = status_name - - def set_description(self, description: str) -> None: - """Sets the description of the Status. - - Args: - status_description (str): The proper description for the status. - """ - self.description = description - - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Status. + status_code (str): Unique code for the status. - This method will set the Status's modified by attribute. - - Args: - modified_by (str): Identifier of the user who modified the Status. + Returns: + self: The instance of the builder. """ - self.modified_by = modified_by + self._dataitem.status_code = status_code + return self - def assign_all_attributes(self, attributes: dict) -> None: - """Sets all attributes of the status. + def set_status_name(self, status_name: str) -> "StatusBuilder": + """Sets the status_name attribute to the passed string. Args: - attributes (dict): The attributes of the status. - Dictionary keys are formatted as the status attribute - names. - """ - self.assign_status_id(attributes["status_id"]) - self.set_status_code(attributes["status_code"]) - self.set_status_name(attributes["status_name"]) - self.set_description(attributes["description"]) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) - - def assign_status_id(self, status_id: int) -> None: - """Manually sets the Status's id. Not be called by the user. - - This method will set the Status's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the Status is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. + status_name (str): Name of the status. - Args: - status_id (int): The Status's numeric id. + Returns: + self: The instance of the builder. """ - self.status_id = status_id + self._dataitem.status_name = status_name + return self - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. - - Note: This method is not intended to be called when building a Status. + def set_description(self, description: str) -> "StatusBuilder": + """Sets the description attribute to the passed string. Args: - created_date (str): The date the Status object was created. Must - be in the format 'YYYY-MM-DD HH:MM:SS'. - """ - self.created_date = database.return_datetime(created_date) - - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. + description (str): Description of the status. - Note: This method is not intended to be called when building a - Status. - - Args: - modified_date (str): The date the Status was last modified. Must - be in the format 'YYYY-MM-DD HH:MM:SS'. + Returns: + self: The instance of the builder. """ - self.modified_date = database.return_datetime(modified_date) + self._dataitem.description = description + return self diff --git a/narcotics_tracker/builders/status_builder_template.py b/narcotics_tracker/builders/status_builder_template.py deleted file mode 100644 index 373e12e..0000000 --- a/narcotics_tracker/builders/status_builder_template.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Contains the template for the Status Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Unit Builder module for more information. - -Classes: - Status: Defines the interface for the Status Builder. -""" -from abc import ABC, abstractmethod - - -class Status(ABC): - """Defines the interface for the Status Builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_status_id(self) -> None: - pass - - @abstractmethod - def set_status_code(self) -> None: - pass - - @abstractmethod - def set_status_name(self) -> None: - pass - - @abstractmethod - def set_description(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def assign_all_attributes(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/builders/unit_builder.py b/narcotics_tracker/builders/unit_builder.py index 09e04bf..de62672 100644 --- a/narcotics_tracker/builders/unit_builder.py +++ b/narcotics_tracker/builders/unit_builder.py @@ -1,171 +1,99 @@ -"""Contains the concrete builder for the Unit class. - -Concrete builders contain the implementation of the builder interface defined -in the abstract builder class. They are used to build objects for the -Narcotics Tracker in a modular step-wise approach. +"""Handles the defining and building of Unit Objects. Classes: - UnitBuilder: Builds and returns Unit objects. + UnitBuilder: Assigns attributes and returns Unit Objects. """ -from narcotics_tracker import database, units -from narcotics_tracker.builders import unit_builder_template - - -class UnitBuilder(unit_builder_template.Unit): - """Builds and returns Unit objects. - - There are two types of methods: 'set' methods can be called to manually - set attributes for the object; 'assign' methods perform calculations and - are used as part of the build method to assign other attributes like the - created date, or unit conversions. - - Look at the Unit Class documentation in the Units Module for - more information on how to use the Unit objects. - - How To Use: - - 1. Initialize the builder by assigning it to a variable: - - `u_builder = unit_builder.UnitBuilder()` - - 2. Call the following methods and pass the required values: - - `set_unit_name()`; `set_unit_code()`; `set_container()`; and - - `set_modified_by()`; +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.units import Unit - 3. Call the `build()` method to return an Unit object. - Initializer: +class UnitBuilder(DataItemBuilder): + """Assigns attributes and returns Unit Objects. - def __init__(self) -> None: + This class inherits methods and attributes from the DataItemBuilder. + Review the documentation for more information. - Initializes unit builder. Sets all attributes to None. + Methods: - Instance Methods: + build: Validates attributes and returns the Unit object. - build: Returns the Unit object. Assigns the Unit's attributes. + set_unit_code: Sets the unit_code attribute to the passed string. - set_unit_code: Sets the unique code of the Unit. + set_unit_name: Sets the unit_name attribute to the passed string. - set_unit_name: Sets the Unit's name. - - set_modified_by: Sets the identifier of the user who modified the - Unit. - - assign_all_attributes: Sets all attributes of the Unit. - - assign_unit_id: Manually sets the Unit's id. Should not be called by - the user. - - assign_created_date: Manually sets the created_date attribute. - - assign_modified_date: Manually sets the modified_date attribute. + set_decimals: Sets the decimals attribute to the passed integer. """ - def __init__(self) -> None: - """Initializes unit builder. Sets all attributes to None.""" - self.unit_id = None - self.unit_code = None - self.unit_name = None - self.created_date = None - self.modified_date = None - self.modified_by = None - - def build(self) -> "units.Unit": - """Returns the Unit object. Assigns the Unit's attributes. - - This is the last method to be called as part of the building process. - It will return the Unit object with all of its attributes set. - The concentration is calculated using the calculate_concentration - method. - - Returns: - unit.Unit: The Unit object. - """ - - return units.Unit(self) - - def set_unit_code(self, unit_code: str) -> None: - """Sets the unique code of the Unit. - - The Units unique code is used to identify the Unit within - the database and the Narcotics Tracker. This code is set by the user - as an easy reference to the Unit. Units without a code are - not retrievable from the database. + _dataitem = Unit( + table="units", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + unit_code=None, + unit_name=None, + decimals=None, + ) + + def _reset(self) -> None: + """Prepares the builder to create a new Unit.""" + self._dataitem = Unit( + table="units", + id=None, + created_date=None, + modified_date=None, + modified_by=None, + unit_code=None, + unit_name=None, + decimals=None, + ) + + def build(self) -> Unit: + """Validates attributes and returns the Unit object.""" + self._dataitem.created_date = self._service_provider.datetime.validate( + self._dataitem.created_date + ) + self._dataitem.modified_date = self._service_provider.datetime.validate( + self._dataitem.modified_date + ) + + unit = self._dataitem + self._reset() + return unit + + def set_unit_code(self, unit_code: str) -> "UnitBuilder": + """Sets the unit_code attribute to the passed string. Args: - code (str): Identifier for this specific Unit. - """ - self.unit_code = unit_code + unit_code (str): Unique code for the unit. - def set_unit_name(self, unit_name: str) -> None: - """Sets the Unit's name. - - Args: - name (str): The Unit's name. + Returns: + self: The instance of the builder. """ - self.unit_name = unit_name + self._dataitem.unit_code = unit_code + return self - def set_modified_by(self, modified_by: str) -> None: - """Sets the identifier of the user who modified the Unit. + def set_unit_name(self, unit_name: str) -> "UnitBuilder": + """Sets the unit_name attribute to the passed string. - This method will set the Unit's modified by attribute. + Args:s + unit_name (str): Name of the unit. - Args: - modified_by (str): Identifier of the user who modified the - Unit. - """ - self.modified_by = modified_by - - def assign_all_attributes(self, attributes: dict) -> None: - """Sets all attributes of the Unit. - - Args: - attributes (dict): The attributes of the Unit. Dictionary keys are - formatted as the Unit attribute names. - """ - self.assign_unit_id(attributes["unit_id"]) - self.set_unit_code(attributes["unit_code"]) - self.set_unit_name(attributes["unit_name"]) - self.assign_created_date(attributes["created_date"]) - self.assign_modified_date(attributes["modified_date"]) - self.set_modified_by(attributes["modified_by"]) - - def assign_unit_id(self, unit_id: int) -> None: - """Manually sets the Unit's id. Should not be called by the user. - - This method will set the Unit's id number. The id number is - generally set by the database using its row id. This method is useful - in setting the id number when the Unit is loaded from the - database. It will override any id number that is already set and may - cause errors in the within the database table. Users should not call - this method. - - Args: - unit_id (int): The Unit's numeric id. + Returns: + self: The instance of the builder. """ - self.unit_id = unit_id - - def assign_created_date(self, created_date: str) -> None: - """Manually sets the created_date attribute. + self._dataitem.unit_name = unit_name + return self - This method will set the Unit's created_date. + def set_decimals(self, decimals: int) -> "UnitBuilder": + """Sets the decimals attribute to the passed integer. Args: - created_date (str): The Unit's created date in the - format YYYY-MM-DD HH:MM:SS. - """ - self.created_date = database.return_datetime(created_date) - - def assign_modified_date(self, modified_date: str) -> None: - """Manually sets the modified_date attribute. - - This method will set the Unit's modified_date. + decimals (int): Number of decimals places. - Args: - modified_date (str): The Unit's created date in the - format YYYY-MM-DD HH:MM:SS. + Returns: + self: The instance of the builder. """ - self.modified_date = database.return_datetime(modified_date) + self._dataitem.decimals = decimals + return self diff --git a/narcotics_tracker/builders/unit_builder_template.py b/narcotics_tracker/builders/unit_builder_template.py deleted file mode 100644 index 90b5ad6..0000000 --- a/narcotics_tracker/builders/unit_builder_template.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Contains the template for the Unit Builder. - -Abstract builders contain no implementation. Look at the documentation for the -Unit Builder module for more information. - -Classes: - Unit: Defines the interface for the Unit builder. -""" -from abc import ABC, abstractmethod - - -class Unit(ABC): - """Defines the interface for the Unit Builder.""" - - @abstractmethod - def __init__(self) -> None: - pass - - @abstractmethod - def assign_unit_id(self) -> None: - pass - - @abstractmethod - def set_unit_code(self) -> None: - pass - - @abstractmethod - def set_unit_name(self) -> None: - pass - - @abstractmethod - def assign_created_date(self) -> None: - pass - - @abstractmethod - def assign_modified_date(self) -> None: - pass - - @abstractmethod - def set_modified_by(self) -> None: - pass - - @abstractmethod - def assign_all_attributes(self) -> None: - pass - - @abstractmethod - def build(self) -> None: - pass diff --git a/narcotics_tracker/commands/__init__.py b/narcotics_tracker/commands/__init__.py new file mode 100644 index 0000000..117c150 --- /dev/null +++ b/narcotics_tracker/commands/__init__.py @@ -0,0 +1,93 @@ +"""Organizes and exports commands for the Narcotics Tracker. + +The Command Pattern was implemented to provider greater flexibility when using +the Narcotics Tracker. The modules within this package contain the various +commands available. They have been imported into this module for easier +importing throughout the project. + +Interfaces: + + Command: Defines the protocol for commands which interact with the SQLite3 + database. + +Modules: + + Adjustment Commands: Contains the commands for Adjustments. + + Event Commands: Contains the commands for Events. + + Medication Commands: Contains the commands for Medications. + + Reporting Period Commands: Contains the commands for Reporting Periods. + + Status Commands: Contains the commands for Statuses. + + Table Commands: Contains commands which created and modify tables in the + SQLite3 database. + + Unit Commands: Contains the commands for Units. + +How To Use: + + Commands allow for their receivers to be set in their initializer. If no + receiver is passes the default service is used. Each command relies on its + `execute` method trigger the command. The execute method accepts any + needed parameters by the receiver. + + ```python + new_adjustment = Adjustment(...) + + commands.AddAdjustment().execute(new_adjustment) + ``` + + ```python + modifier = command.ReturnEventModifier("LOSS") + ``` +""" + +from narcotics_tracker.commands.adjustment_commands import ( + AddAdjustment, + DeleteAdjustment, + ListAdjustments, + UpdateAdjustment, +) +from narcotics_tracker.commands.event_commands import ( + AddEvent, + DeleteEvent, + ListEvents, + ReturnEventModifier, + UpdateEvent, +) +from narcotics_tracker.commands.medication_commands import ( + AddMedication, + DeleteMedication, + ListMedications, + ReturnPreferredUnit, + UpdateMedication, +) +from narcotics_tracker.commands.reporting_period_commands import ( + AddReportingPeriod, + DeleteReportingPeriod, + ListReportingPeriods, + UpdateReportingPeriod, +) +from narcotics_tracker.commands.status_commands import ( + AddStatus, + DeleteStatus, + ListStatuses, + UpdateStatus, +) +from narcotics_tracker.commands.table_commands import ( + CreateEventsTable, + CreateInventoryTable, + CreateMedicationsTable, + CreateReportingPeriodsTable, + CreateStatusesTable, + CreateUnitsTable, +) +from narcotics_tracker.commands.unit_commands import ( + AddUnit, + DeleteUnit, + ListUnits, + UpdateUnit, +) diff --git a/narcotics_tracker/commands/adjustment_commands.py b/narcotics_tracker/commands/adjustment_commands.py new file mode 100644 index 0000000..27abfe2 --- /dev/null +++ b/narcotics_tracker/commands/adjustment_commands.py @@ -0,0 +1,150 @@ +"""Contains the commands for Adjustments. + +Please see the package documentation for more information. + +Classes: + + AddAdjustment: Adds an Adjustment to the database. + + DeleteAdjustment: Deletes a Adjustment from the database by its ID or code. + + ListAdjustments: Returns a list of Adjustments. + + UpdateAdjustment: Updates a Event with the given data and criteria. +""" +from typing import TYPE_CHECKING + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.adjustments import Adjustment + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class AddAdjustment(Command): + """Adds an Adjustment to the database. + + Methods: + execute: Executes add row operation, returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, adjustment: "Adjustment") -> str: + """Executes add row operation, returns a success message. + + Args: + adjustment (Adjustment): The Adjustment object to be added to the + database. + """ + adjustment_info = vars(adjustment) + table_name = adjustment_info.pop("table") + + self._receiver.add(table_name, adjustment_info) + + return f"Adjustment added to {table_name} table." + + +class DeleteAdjustment(Command): + """Deletes an Adjustment from the database by its ID. + + Methods: + execute: Executes the delete operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, adjustment_id: int) -> str: + """Execute the delete operation and returns a success message.""" + self._receiver.remove("inventory", {"id": adjustment_id}) + + return f"Adjustment #{adjustment_id} deleted." + + +class ListAdjustments(Command): + """Returns a list of Adjustments. + + Methods: + execute: Executes the command and returns a list of Adjustment. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, criteria: dict[str] = {}, order_by: str = None) -> list[tuple]: + """Executes the command and returns a list of Adjustments. + + Args: + criteria (dict[str, any]): The criteria of Adjustments to be + returned as a dictionary mapping column names to their values. + + order_by (str): The column name by which the results will be + sorted. + """ + cursor = self._receiver.read("inventory", criteria, order_by) + return cursor.fetchall() + + +class UpdateAdjustment(Command): + """Updates an Adjustment with the given data and criteria. + + Method: + execute: Executes the update operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: + """Executes the update operation and returns a success message. + + Args: + data (dict[str, any]): The new data to update the Adjustment + with as a dictionary mapping column names to their values. + + criteria (dict[str, any]): The criteria to select which + Adjustments are to be updated as a dictionary mapping the + column name to its value. + """ + self._receiver.update("inventory", data, criteria) + + return f"Adjustment data updated." diff --git a/narcotics_tracker/commands/event_commands.py b/narcotics_tracker/commands/event_commands.py new file mode 100644 index 0000000..279a007 --- /dev/null +++ b/narcotics_tracker/commands/event_commands.py @@ -0,0 +1,191 @@ +"""Contains the commands for Events. + +Please see the package documentation for more information. + +Classes: + + AddEvent: Adds an Event to the database. + + DeleteEvent: Deletes a Event from the database by its ID or code. + + ListEvents: Returns a list of Events. + + UpdateEvent: Updates a Event with the given data and criteria. + + ReturnEventModifier: Returns an Event's modifier. +""" +from typing import TYPE_CHECKING, Union + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.events import Event + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class AddEvent(Command): + """Adds an Event to the database. + + Methods: + execute: Executes add row operation, returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, event: "Event") -> str: + """Executes add row operation, returns a success message. + + Args: + event (Event): The Event object to be added to the + database. + """ + event_info = vars(event) + table_name = event_info.pop("table") + + self._receiver.add(table_name, event_info) + + return f"Event added to {table_name} table." + + +class DeleteEvent(Command): + """Deletes a Event from the database by its ID or code. + + Methods: + execute: Executes the delete operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, event_identifier: Union[int, str]) -> str: + """Executes the delete operation and returns a success message. + + Args: + event_identifier (str, int): The event code or id number + of the Event to be deleted. + """ + if type(event_identifier) is int: + criteria = {"id": event_identifier} + + if type(event_identifier) is str: + criteria = {"event_code": event_identifier} + + self._receiver.remove("events", criteria) + + return f"Event {event_identifier} deleted." + + +class ListEvents(Command): + """Returns a list of Events. + + Methods: + execute: Executes the command and returns a list of Events. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute( + self, criteria: dict[str, any] = {}, order_by: str = None + ) -> list[tuple]: + """Executes the command and returns a list of Events. + + Args: + criteria (dict[str, any]): The criteria of Events to be + returned as a dictionary mapping column names to their values. + + order_by (str): The column name by which the results will be + sorted. + """ + cursor = self._receiver.read("events", criteria, order_by) + return cursor.fetchall() + + +class UpdateEvent(Command): + """Updates a Event with the given data and criteria. + + Method: + execute: Executes the update operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: + """Executes the update operation and returns a success message. + + Args: + data (dict[str, any]): The new data to update the Event + with as a dictionary mapping column names to their values. + + criteria (dict[str, any]): The criteria to select which + Events are to be updated as a dictionary mapping the + column name to its value. + """ + self._receiver.update("events", data, criteria) + + return f"Event data updated." + + +class ReturnEventModifier(Command): + """Returns an Event's modifier. + + Methods: + execute: Executes the command and returns the modifier. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, event_code: str) -> int: + """Executes the command and returns the modifier.""" + criteria = {"event_code": event_code} + cursor = self._receiver.read("events", criteria) + return cursor.fetchall()[0][4] diff --git a/narcotics_tracker/commands/interfaces/__init__.py b/narcotics_tracker/commands/interfaces/__init__.py new file mode 100644 index 0000000..a3ee6ec --- /dev/null +++ b/narcotics_tracker/commands/interfaces/__init__.py @@ -0,0 +1,6 @@ +"""Contains the interfaces for the Commands Module. + +Modules: + command: Defines the protocol for commands which interact with the SQLite3 + database. +""" diff --git a/narcotics_tracker/commands/interfaces/command.py b/narcotics_tracker/commands/interfaces/command.py new file mode 100644 index 0000000..b2c031f --- /dev/null +++ b/narcotics_tracker/commands/interfaces/command.py @@ -0,0 +1,25 @@ +"""Defines the protocol for commands which interact with the SQLite3 database. + +Classes: + SQLiteCommand: The interface for SQLite3 database commands. + +""" +from typing import Protocol + + +class Command(Protocol): + """The interface for SQLite3 database commands. + + Required method signatures: + def __init__(self, receiver) -> None: + + def execute(self) -> None: + """ + + def __init__(self, receiver) -> None: + """Sets the receiver of the command.""" + ... + + def execute(self) -> None: + """Executes the command. Accepts parameters required by the receiver.""" + ... diff --git a/narcotics_tracker/commands/medication_commands.py b/narcotics_tracker/commands/medication_commands.py new file mode 100644 index 0000000..64eebf4 --- /dev/null +++ b/narcotics_tracker/commands/medication_commands.py @@ -0,0 +1,192 @@ +"""Contains the commands for Medications. + +Please see the package documentation for more information. + +Classes: + + AddMedication: Adds an Medication to the database. + + DeleteMedication: Deletes a Medication from the database by its ID or code. + + ListMedications: Returns a list of Medications. + + UpdateMedication: Updates a Medication with the given data and criteria. + + ReturnPreferredUnit: Returns the preferred unit for the specified + Medication. +""" +from typing import TYPE_CHECKING, Union + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.medications import Medication + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class AddMedication(Command): + """Adds a Medication to the database. + + Methods: + execute: Executes add row operation, returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, medication: "Medication") -> str: + """Executes add row operation, returns a success message. + + Args: + medication (Medication): The Medication object to be added to the + database. + """ + medication_info = vars(medication) + table_name = medication_info.pop("table") + + self._receiver.add(table_name, medication_info) + + return f"Medication added to {table_name} table." + + +class DeleteMedication(Command): + """Deletes a Medication from the database by its ID or code. + + Methods: + execute: Executes the delete operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, medication_identifier: Union[str, int]) -> str: + """Executes the delete operation and returns a success message. + + Args: + medication_identifier (str, int): The medication code or id number + of the Medication to be deleted. + """ + if type(medication_identifier) is int: + criteria = {"id": medication_identifier} + + if type(medication_identifier) is str: + criteria = {"medication_code": medication_identifier} + + self._receiver.remove("medications", criteria) + + return f"Medication {medication_identifier} deleted." + + +class ListMedications(Command): + """Returns a list of Medications. + + Methods: + execute: Executes the command and returns a list of Medications. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute( + self, criteria: dict[str, any] = {}, order_by: str = None + ) -> list[tuple]: + """Executes the command and returns a list of Medications. + + Args: + criteria (dict[str, any]): The criteria of Medications to be + returned as a dictionary mapping column names to their values. + + order_by (str): The column name by which the results will be + sorted. + """ + cursor = self._receiver.read("medications", criteria, order_by) + return cursor.fetchall() + + +class UpdateMedication(Command): + """Updates a Medication with the given data and criteria. + + Method: + execute: Executes the update operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: + """Executes the update operation and returns a success message. + + Args: + data (dict[str, any]): The new data to update the Medication + with as a dictionary mapping column names to their values. + + criteria (dict[str, any]): The criteria to select which + Medications are to be updated as a dictionary mapping the + column name to its value. + """ + self._receiver.update("medications", data, criteria) + + return f"Medication data updated." + + +class ReturnPreferredUnit(Command): + """Returns the preferred unit for the specified Medication. + + Methods: + execute: Executes the command, returns results.""" + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, medication_code: str) -> str: + """Executes the command, returns results.""" + criteria = {"medication_code": medication_code} + + cursor = self._receiver.read("medications", criteria) + return cursor.fetchall()[0][4] diff --git a/narcotics_tracker/commands/reporting_period_commands.py b/narcotics_tracker/commands/reporting_period_commands.py new file mode 100644 index 0000000..3a4cb20 --- /dev/null +++ b/narcotics_tracker/commands/reporting_period_commands.py @@ -0,0 +1,156 @@ +"""Contains the commands for Reporting Periods. + +Please see the package documentation for more information. + +Classes: + AddReportingPeriod: Adds a Reporting Period to the database. + + DeleteReportingPeriod: Deletes a Reporting Period from the database by its + ID or code. + + ListReportingPeriods: Returns a list of Reporting Periods. + + UpdateReportingPeriod: Updates a Reporting Period with the given data and + criteria. +""" +from typing import TYPE_CHECKING + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.reporting_periods import ReportingPeriod + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class AddReportingPeriod(Command): + """Adds a Reporting Period to the database. + + Methods: + execute: Executes add row operation, returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, reporting_period: "ReportingPeriod") -> str: + """Executes add row operation, returns a success message. + + Args: + reporting_period (ReportingPeriod): The Reporting Period object to + be added to the database. + """ + reporting_period_info = vars(reporting_period) + table_name = reporting_period_info.pop("table") + + self._receiver.add(table_name, reporting_period_info) + + return f"Reporting Period added to {table_name} table." + + +class DeleteReportingPeriod(Command): + """Deletes a Reporting Period from the database by its ID. + + Methods: + execute: Executes the delete operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, reporting_period_id: int) -> str: + """Executes the delete operation and returns a success message. + + Args: + reporting_period_identifier (int): The id number of the Reporting + Period to be deleted. + """ + self._receiver.remove("reporting_periods", {"id": reporting_period_id}) + + return f"Reporting Period #{reporting_period_id} deleted." + + +class ListReportingPeriods(Command): + """Returns a list of Reporting Periods. + + Methods: + execute: Executes the command and returns a list of Reporting Periods. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, criteria: dict[str] = {}, order_by: str = None) -> list[tuple]: + """Executes the command and returns a list of Reporting Periods. + + Args: + criteria (dict[str, any]): The criteria of Reporting Periods to be + returned as a dictionary mapping column names to their values. + + order_by (str): The column name by which the results will be + sorted. + """ + cursor = self._receiver.read("reporting_periods", criteria, order_by) + return cursor.fetchall() + + +class UpdateReportingPeriod(Command): + """Updates a Reporting Period with the given data and criteria. + + Method: + execute: Executes the update operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: + """Executes the update operation and returns a success message. + + Args: + data (dict[str, any]): The new data to update the ReportingPeriod + with as a dictionary mapping column names to their values. + + criteria (dict[str, any]): The criteria to select which + ReportingPeriods are to be updated as a dictionary mapping the + column name to its value. + """ + self._receiver.update("reporting_periods", data, criteria) + + return f"Reporting Period data updated." diff --git a/narcotics_tracker/commands/status_commands.py b/narcotics_tracker/commands/status_commands.py new file mode 100644 index 0000000..5c8e4d9 --- /dev/null +++ b/narcotics_tracker/commands/status_commands.py @@ -0,0 +1,159 @@ +"""Contains the commands for Statuses. + +Please see the package documentation for more information. + +Classes: + AddStatus: Adds a Status to the database. + + DeleteStatus: Deletes a Status from the database by its ID or code. + + ListStatuses: Returns a list of Statuses. + + UpdateStatus: Updates a Status with the given data and criteria. +""" +from typing import TYPE_CHECKING, Union + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.statuses import Status + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class AddStatus(Command): + """Adds a Status to the database. + + Methods: + execute: Executes add row operation, returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, status: "Status") -> str: + """Executes add row operation, returns a success message. + + Args: + status (Status): The Status object to be added to the database. + """ + status_info = vars(status) + table_name = status_info.pop("table") + + self._receiver.add(table_name, status_info) + + return f"Status added to {table_name} table." + + +class DeleteStatus(Command): + """Deletes a Status from the database by its ID or code. + + Methods: + execute: Executes the delete operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, status_identifier: Union[str, int]) -> str: + """Executes the delete operation and returns a success message. + + Args: + status_identifier (str, int): The status code or id number of the + Status to be deleted. + """ + if type(status_identifier) is int: + criteria = {"id": status_identifier} + + if type(status_identifier) is str: + criteria = {"status_code": status_identifier} + + self._receiver.remove("statuses", criteria) + + return f"Status {status_identifier} deleted." + + +class ListStatuses(Command): + """Returns a list of Statuses. + + Methods: + execute: Executes the command and returns a list of Statuses. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, criteria: dict[str] = {}, order_by: str = None) -> list[tuple]: + """Executes the command and returns a list of Statuses. + + Args: + criteria (dict[str, any]): The criteria of Statuses to be returned + as a dictionary mapping column names to their values. + + order_by (str): The column name by which the results will be + sorted. + """ + cursor = self._receiver.read("statuses", criteria, order_by) + return cursor.fetchall() + + +class UpdateStatus(Command): + """Updates a Status with the given data and criteria. + + Methods: + execute: Executes the update operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: + """Executes the update operation and returns a success message. + + Args: + data (dict[str, any]): The new data to update the Status with as a + dictionary mapping column names to their values. + + criteria (dict[str, any]): The criteria to select which Statuses + are to be updated as a dictionary mapping the column name to + its value. + """ + self._receiver.update("statuses", data, criteria) + + return f"Status data updated." diff --git a/narcotics_tracker/commands/table_commands.py b/narcotics_tracker/commands/table_commands.py new file mode 100644 index 0000000..21f63a1 --- /dev/null +++ b/narcotics_tracker/commands/table_commands.py @@ -0,0 +1,263 @@ +"""Contains commands which created and modify tables in the SQLite3 database. + +Please review the package documentation for information on using commands. + +Classes: + + CreateEventsTable: Creates the 'events' table in the SQLite3 database. + + CreateInventoryTable: Creates the 'inventory' table in the SQLite3 + database. + + CreateMedicationsTable: Creates the 'medications' table in the SQLite3 + database. + + CreateReportingPeriodsTable: Creates the 'reporting_periods' table in the + SQLite3 database. + + CreateStatusesTable: Creates the 'statuses' table in the SQLite3 database. + + CreateUnitsTable: Creates the 'units' table in the SQLite3 database. +""" +from typing import TYPE_CHECKING + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class CreateEventsTable(Command): + """Creates the 'events' table in the SQLite3 database. + + Methods: + execute: Executes the command. + """ + + _table_name = "events" + _column_info = { + "id": "INTEGER PRIMARY KEY", + "event_code": "TEXT NOT NULL UNIQUE", + "event_name": "TEXT NOT NULL", + "description": "TEXT NOT NULL", + "modifier": "INTEGER NOT NULL", + "created_date": "INTEGER NOT NULL", + "modified_date": "INTEGER NOT NULL", + "modified_by": "TEXT NOT NULL", + } + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self): + """Executes the command.""" + self._receiver.create_table(self._table_name, self._column_info) + + +class CreateInventoryTable(Command): + """Creates the 'inventory' table in the SQLite3 database. + + Methods: + execute: Executes the command. + """ + + _table_name = "inventory" + _column_info = { + "id": "INTEGER PRIMARY KEY", + "adjustment_date": "INTEGER NOT NULL", + "event_code": "TEXT NOT NULL", + "medication_code": "TEXT NOT NULL", + "amount": "REAL NOT NULL", + "reporting_period_id": "INTEGER NOT NULL", + "reference_id": "TEXT NOT NULL", + "created_date": "INTEGER NOT NULL", + "modified_date": "INTEGER NOT NULL", + "modified_by": "TEXT NOT NULL", + } + + _foreign_key_info = [ + "FOREIGN KEY (event_code) REFERENCES events (event_code) ON UPDATE CASCADE", + "FOREIGN KEY (medication_code) REFERENCES medications (medication_code) ON UPDATE CASCADE", + "FOREIGN KEY (reporting_period_id) REFERENCES reporting_periods (id) ON UPDATE CASCADE", + ] + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self): + """Executes the command.""" + self._receiver.create_table( + table_name=self._table_name, + column_info=self._column_info, + foreign_key_info=self._foreign_key_info, + ) + + +class CreateMedicationsTable(Command): + """Creates the 'medications' table in the SQLite3 database. + + Methods: + execute: Executes the command. + """ + + _table_name = "medications" + _column_info = { + "id": "INTEGER PRIMARY KEY", + "medication_code": "TEXT NOT NULL UNIQUE", + "medication_name": "TEXT NOT NULL", + "medication_amount": "REAL NOT NULL", + "preferred_unit": "TEXT NOT NULL", + "fill_amount": "REAL NOT NULL", + "concentration": "REAL NOT NULL", + "status": "TEXT NOT NULL", + "created_date": "INTEGER NOT NULL", + "modified_date": "INTEGER NOT NULL", + "modified_by": "TEXT NOT NULL", + } + + _foreign_key_info = [ + "FOREIGN KEY (preferred_unit) REFERENCES units (unit_code) ON UPDATE CASCADE", + "FOREIGN KEY (status) REFERENCES statuses (status_code) ON UPDATE CASCADE", + ] + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self): + """Executes the command.""" + self._receiver.create_table( + self._table_name, self._column_info, self._foreign_key_info + ) + + +class CreateReportingPeriodsTable(Command): + """Creates the 'reporting_periods' table in the SQLite3 database. + + Methods: + execute: Executes the command. + """ + + _table_name = "reporting_periods" + _column_info = { + "id": "INTEGER PRIMARY KEY", + "start_date": "INTEGER NOT NULL", + "end_date": "INTEGER", + "status": "TEXT NOT NULL", + "created_date": "INTEGER NOT NULL", + "modified_date": "INTEGER NOT NULL", + "modified_by": "TEXT NOT NULL", + } + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self): + """Executes the command.""" + self._receiver.create_table(self._table_name, self._column_info) + + +class CreateStatusesTable(Command): + """Creates the 'statuses' table in the SQLite3 database. + + Methods: + execute: Executes the command. + """ + + _table_name = "statuses" + _column_info = { + "id": "INTEGER PRIMARY KEY", + "status_code": "TEXT NOT NULL UNIQUE", + "status_name": "TEXT NOT NULL", + "description": "TEXT NOT NULL", + "created_date": "INTEGER NOT NULL", + "modified_date": "INTEGER NOT NULL", + "modified_by": "TEXT NOT NULL", + } + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self): + """Executes the command.""" + self._receiver.create_table(self._table_name, self._column_info) + + +class CreateUnitsTable(Command): + """Creates the 'units' table in the SQLite3 database. + + Methods: + execute: Executes the command. + """ + + _table_name = "units" + _column_info = { + "id": "INTEGER PRIMARY KEY", + "unit_code": "TEXT NOT NULL UNIQUE", + "unit_name": "TEXT NOT NULL", + "decimals": "INTEGER NOT NULL", + "created_date": "INTEGER NOT NULL", + "modified_date": "INTEGER NOT NULL", + "modified_by": "TEXT NOT NULL", + } + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self): + """Executes the command.""" + self._receiver.create_table(self._table_name, self._column_info) diff --git a/narcotics_tracker/commands/unit_commands.py b/narcotics_tracker/commands/unit_commands.py new file mode 100644 index 0000000..8004f14 --- /dev/null +++ b/narcotics_tracker/commands/unit_commands.py @@ -0,0 +1,161 @@ +"""Contains the commands for Units. + +Please see the package documentation for more information. + +Classes: + + AddUnit: Adds an Unit to the database. + + DeleteUnit: Deletes a Unit from the database by its ID or code. + + ListUnits: Returns a list of Units. + + UpdateUnit: Updates a Unit with the given data and criteria. +""" +from typing import TYPE_CHECKING, Union + +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.items.units import Unit + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class AddUnit(Command): + """Adds an Unit to the database. + + Methods: + execute: Executes add row operation, returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, unit: "Unit") -> str: + """Executes add row operation, returns a success message. + + Args: + unit (Unit): The Unit object to be added to the database. + """ + unit_info = vars(unit) + table_name = unit_info.pop("table") + + self._receiver.add(table_name, unit_info) + + return f"Unit added to {table_name} table." + + +class DeleteUnit(Command): + """Deletes a Unit from the database by its ID or code. + + Methods: + execute: Executes the delete operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """ "Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, unit_identifier: Union[str, int]) -> str: + """Executes the delete operation and returns a success message. + + Args: + unit_identifier (str, int): The unit code or id number of the unit + to be deleted. + """ + if type(unit_identifier) is int: + criteria = {"id": unit_identifier} + + if type(unit_identifier) is str: + criteria = {"unit_code": unit_identifier} + + self._receiver.remove("units", criteria) + + return f"Unit {unit_identifier} deleted." + + +class ListUnits(Command): + """Returns a list of Units. + + execute: Executes the query and returns a list of Units. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute( + self, criteria: dict[str, any] = {}, order_by: str = None + ) -> list[tuple]: + """Executes the query and returns a list of Units. + + Args: + criteria (dict[str, any]): The criteria of Units to be returned as + a dictionary mapping column names to their values. + + order_by (str): The column name by which the results will be + sorted. + """ + cursor = self._receiver.read("units", criteria, order_by) + return cursor.fetchall() + + +class UpdateUnit(Command): + """Updates a Unit with the given data and criteria. + + Method: + execute: Executes the update operation and returns a success message. + """ + + def __init__(self, receiver: "PersistenceService" = None) -> None: + """Initializes the command. Sets the receiver if passed. + + Args: + receiver (PersistenceService, optional): Object which communicates + with the data repository. Defaults to SQLiteManager. + """ + if receiver: + self._receiver = receiver + else: + self._receiver = ServiceManager().persistence + + def execute(self, data: dict[str, any], criteria: dict[str, any]) -> str: + """Executes the update operation and returns a success message. + + Args: + data (dict[str, any]): The new data to update the Unit with as a + dictionary mapping column names to their values. + + criteria (dict[str, any]): The criteria to select which units are + to be updated as a dictionary mapping the column name to its + value. + """ + self._receiver.update("units", data, criteria) + + return f"Unit data updated." diff --git a/narcotics_tracker/configuration/__init__.py b/narcotics_tracker/configuration/__init__.py new file mode 100644 index 0000000..bdf694f --- /dev/null +++ b/narcotics_tracker/configuration/__init__.py @@ -0,0 +1,31 @@ +"""Contains the modules needed to set up the Narcotics Tracker software. + +The events, statuses, and units table serve as vocabulary control tables which +provide the available events, statuses and units which can be used in other +tables. This package and its modules create the standard items which are used +at all EMS agencies. They can be returned in lists and easily added to the +data repository. + +Modules: + + standard_items: Defines the standard DataItems used for inventory tracking. + +How To Use: + + The StandardItemCreator contains methods which build and return the + standard items as needed. + + ```python + standard_events = StandardItemCreator().create_events() + for event in standard_events: + command.AddEvent().execute(event) + + standard_statuses = StandardItemCreator().create_statuses() + for status in standard_statuses: + command.AddStatus().execute(status) + + standard_events = StandardItemCreator().create_events() + for unit in standard_units: + command.AddUnit().execute(unit) + ``` +""" diff --git a/narcotics_tracker/configuration/standard_items.py b/narcotics_tracker/configuration/standard_items.py new file mode 100644 index 0000000..3f07026 --- /dev/null +++ b/narcotics_tracker/configuration/standard_items.py @@ -0,0 +1,293 @@ +"""Defines the standard DataItems used for inventory tracking. + +Classes: + + StandardItemCreator: Creates standard DataItems for the Narcotics Tracker. +""" +from typing import TYPE_CHECKING + +from narcotics_tracker.builders.event_builder import EventBuilder +from narcotics_tracker.builders.status_builder import StatusBuilder +from narcotics_tracker.builders.unit_builder import UnitBuilder + +if TYPE_CHECKING: + from narcotics_tracker.items.events import Event + from narcotics_tracker.items.statuses import Status + from narcotics_tracker.items.units import Unit + + +class StandardItemCreator: + """Creates standard DataItems for the Narcotics Tracker. + + Methods: + + create_events: Creates standard events and returns them in a list. + + create_statuses: Creates standard statuses and returns them in a list. + + create_units: Creates standard units and returns them in a list. + """ + + _standard_events = [] + _standard_statuses = [] + _standard_units = [] + + def create_events(self) -> list["Event"]: + """Creates standard events and returns them in a list.""" + self._create_standard_events() + + return self._standard_events + + def create_statuses(self) -> list["Status"]: + """Creates standard statuses and returns them in a list.""" + self._create_standard_statuses() + + return self._standard_statuses + + def create_units(self) -> list["Unit"]: + """Creates standard units and returns them in a list.""" + self._create_standard_units() + + return self._standard_units + + def _create_standard_events(self) -> list["Event"]: + """Builds the standard events and returns in a list.""" + destroy_event = ( + EventBuilder() + .set_table("events") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_event_code("DESTROY") + .set_event_name("Destroyed") + .set_description( + "Used when subtracting medication which was destroyed through a reverse distributor." + ) + .set_modifier(-1) + .build() + ) + self._standard_events.append(destroy_event) + + import_event = ( + EventBuilder() + .set_table("events") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_event_code("IMPORT") + .set_event_name("Imported") + .set_description("Used when adding pre-existing stock to the inventory.") + .set_modifier(+1) + .build() + ) + self._standard_events.append(import_event) + + loss_event = ( + EventBuilder() + .set_table("events") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_event_code("LOSS") + .set_event_name("Lost") + .set_description( + "Used when subtracting medication which were lost or stolen." + ) + .set_modifier(-1) + .build() + ) + self._standard_events.append(loss_event) + + order_event = loss_event = ( + EventBuilder() + .set_table("events") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_event_code("ORDER") + .set_event_name("Ordered") + .set_description("Used when adding new stock from a purchase order.") + .set_modifier(+1) + .build() + ) + self._standard_events.append(order_event) + + use_event = ( + EventBuilder() + .set_table("events") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_event_code("USE") + .set_event_name("Used") + .set_description( + "Used when subtracting medication that was administered to a patient." + ) + .set_modifier(-1) + .build() + ) + self._standard_events.append(use_event) + + waste_event = ( + EventBuilder() + .set_table("events") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_event_code("WASTE") + .set_event_name("Wasted") + .set_description("Used when subtracting medication which was wasted.") + .set_modifier(-1) + .build() + ) + self._standard_events.append(waste_event) + + def _create_standard_units(self) -> list["Unit"]: + """Builds the standard units and returns in a list.""" + microgram = ( + UnitBuilder() + .set_id() + .set_table("units") + .set_created_date() + .set_modified_date() + .set_modified_by("Setup") + .set_unit_code("mcg") + .set_unit_name("microgram") + .set_decimals(-6) + .build() + ) + self._standard_units.append(microgram) + + milligram = ( + UnitBuilder() + .set_id() + .set_table("units") + .set_created_date() + .set_modified_date() + .set_modified_by("Setup") + .set_unit_code("mg") + .set_unit_name("milligram") + .set_decimals(-3) + .build() + ) + self._standard_units.append(milligram) + + gram = ( + UnitBuilder() + .set_id() + .set_table("units") + .set_created_date() + .set_modified_date() + .set_modified_by("Setup") + .set_unit_code("g") + .set_unit_name("gram") + .set_decimals(0) + .build() + ) + self._standard_units.append(gram) + + milliliter = ( + UnitBuilder() + .set_id() + .set_table("units") + .set_created_date() + .set_modified_date() + .set_modified_by("Setup") + .set_unit_code("ml") + .set_unit_name("milliliter") + .set_decimals(-3) + .build() + ) + self._standard_units.append(milliliter) + + standard_unit = ( + UnitBuilder() + .set_id() + .set_table("units") + .set_created_date() + .set_modified_date() + .set_modified_by("Setup") + .set_unit_code("std") + .set_unit_name("standard") + .set_decimals(-8) + .build() + ) + self._standard_units.append(standard_unit) + + def _create_standard_statuses(self) -> list["Status"]: + """Builds the standard statuses and returns in a list.""" + active_status = ( + StatusBuilder() + .set_table("statuses") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_status_code("ACTIVE") + .set_status_name("Active") + .set_description("Used for items which are still being used.") + .build() + ) + self._standard_statuses.append(active_status) + + inactive_status = ( + StatusBuilder() + .set_table("statuses") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_status_code("INACTIVE") + .set_status_name("Inactive") + .set_description("Used for items which are no longer being used.") + .build() + ) + self._standard_statuses.append(inactive_status) + + open_status = ( + StatusBuilder() + .set_table("statuses") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_status_code("OPEN") + .set_status_name("Open") + .set_description("Used for items which have not been completed.") + .build() + ) + self._standard_statuses.append(open_status) + + closed_status = ( + StatusBuilder() + .set_table("statuses") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_status_code("CLOSED") + .set_status_name("Closed") + .set_description("Used for items which have been completed.") + .build() + ) + self._standard_statuses.append(closed_status) + + cancelled_status = ( + StatusBuilder() + .set_table("statuses") + .set_id(None) + .set_created_date() + .set_modified_date() + .set_modified_by("System") + .set_status_code("CANCELLED") + .set_status_name("Cancelled") + .set_description("Used for items which have been cancelled.") + .build() + ) + self._standard_statuses.append(cancelled_status) diff --git a/narcotics_tracker/containers.py b/narcotics_tracker/containers.py deleted file mode 100644 index 53320f5..0000000 --- a/narcotics_tracker/containers.py +++ /dev/null @@ -1,334 +0,0 @@ -"""Contains implementation and representation of Medication Containers. - -#* Background - -All controlled substance medications come in containers which store a specific -amount of the medication and its solvent. When purchasing medications they are -often ordered by the container. All Medications require their container to be -specified. - -#* Intended Use - -The module and the Container Class defined below allow for the creation of -Container Objects. It is highly recommended to use the Container Builder -Module contained within the Builders Package to create these objects. -Instructions for using builders can be found within that package. - -#* Containers in the Database - -Containers are stored in the 'containers' table of the database with their -numeric id, code, name and creation / modification information specified. -Medication objects must declare which container the medication comes in and -are limited to the items listed in the 'containers' table. - -The Narcotics Tracker comes with a selection of pre-defined containers. Refer -to the Standard Items Module inside the Setup Package for more information. - -#* Classes: - - Container: Defines Containers and instantiates them as objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the - 'containers' table. - - return_containers: Returns the 'containers' table as lists of strings and - values. - - parse_container_data: Returns a Container's attributes as a dictionary. -""" -import sqlite3 -from typing import Union - -from narcotics_tracker import database - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'containers' table. - - Returns: - - str: The sql query needed to create the 'containers' table. - """ - return """CREATE TABLE IF NOT EXISTS containers ( - CONTAINER_ID INTEGER PRIMARY KEY, - CONTAINER_CODE TEXT UNIQUE, - CONTAINER_NAME TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - -def return_containers(db_connection: sqlite3.Connection) -> Union[list[str], list]: - """Returns the 'containers' table as lists of strings and values. - - Args: - - db_connection (sqlite3.Connection): The database connection. - - Returns: - - containers_string_list (list[str]): The contents of the table as a list of - strings. - - containers_values_list (list): The contents of the table as a list of - values. - """ - sql_query = ( - """SELECT container_id, container_code, container_name FROM containers""" - ) - - containers_strings_list = [] - containers_values_list = [] - - containers_data = db_connection.return_data(sql_query) - for container in containers_data: - containers_strings_list.append( - f"Container {container[0]}: {container[2]}. Code: '{container[1]}'." - ) - containers_values_list.append((container[0], container[1], container[2])) - - return containers_strings_list, containers_values_list - - -def parse_container_data(container_data) -> dict: - """Returns a Container's attributes as a dictionary. - - Args: - - container_data (list): The Container's data. - - Returns: - - attributes (dict): Dictionary object containing the attributes of the - Container. - """ - attributes = {} - - attributes["container_id"] = container_data[0][0] - attributes["container_code"] = container_data[0][1] - attributes["container_name"] = container_data[0][2] - attributes["created_date"] = container_data[0][3] - attributes["modified_date"] = container_data[0][4] - attributes["modified_by"] = container_data[0][5] - - return attributes - - -class Container: - """Defines Containers and instantiates them as objects. - - This class defines Containers within the Narcotics Tracker. Containers are - the vessels which hold medications. When controlled substance medications - are ordered an amount of containers is specified. Each Medication must - specify the container they come in and are limited to the items listed in - the 'containers' table. - - Containers can be declared, created and managed using this class. They are - stored in the 'containers' table. - - Attributes: - - container_id (int): Numeric identifier of each Container. Assigned by - the database. - - container_code (str): Unique identifier of each Container. Assigned by - the user. Used to interact with the Container in the database. - - container_code (str): Name of the Container. - - created_date (str): The date the Container was created in the - table. - - modified_date (str): The date the Container was last modified. - - modified_by (str): Identifier of the user who last modified the - Container. - - Initializer: - - def __init__(self, builder=None) -> None: - - Initializes an instance of n Container using the builder. - - Instance Methods: - __repr__: Returns a string expression of the Container Object. - - save: Saves a new Container to the table in the database. - - read: Returns the data of the Container as a tuple. - - update: Updates the Container in the 'containers' table. - - return_attributes: Returns the attributes of the Container Object as a - tuple. - - delete: Deletes the Container from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of n Container using the builder. - - Containers are complex objects with many attributes. The Builder - Pattern was used to separate the creation of Containers to the Builder - Package. - - Refer to the documentation for the ContainerBuilder Class for more - information. - - Args: - - builder (container_builder.ContainerBuilder): The builder used - to construct the Container Object. - """ - self.container_id = builder.container_id - self.container_code = builder.container_code - self.container_name = builder.container_name - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """Returns a string expression of the Container Object. - - Returns: - - str: The string describing the Container Object. - """ - return f"Unit Number {self.container_id}: {self.container_name}. Code: '{self.container_code}'." - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new Container to the table in the database. - - This method will not overwrite a Container already saved in the - database. Use the `update()` to adjust a Container's attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - sql_query = """INSERT OR IGNORE INTO containers VALUES ( - ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the Container as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the Container's attribute values - in the order of the 'containers' table's columns. - """ - sql_query = """SELECT * from containers WHERE container_code = ?""" - - values = (self.container_code,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the Container in the 'containers' table. - - This method will overwrite the Container's data if it already exists - within the database. An error will be returned if the container_code - does not already exist in the database. Use the save method to save - new Containers in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - - Raises: - - IndexError: An Index Error will be raised if the container_code is - not found on the Containers table. - - How to use: - - 1. Use the `containers.return_containers()` method to return a - list of Containers. Identify the container_code of the - Container you wish to update. - - 2. Use the database.load_container() method, passing in the - container_code and assigning it to a variable to create a - Container Object. - - 3. Modify the attributes as necessary and call this method on the - Container Object to send the new values to the database. - - #! Note: If the container_code is being changed use the save() - #! method to create a new Container entry in the table and use - #! the delete() method to remove the old entry. - """ - sql_query = """UPDATE containers - SET container_id = ?, - container_code = ?, - container_name = ?, - created_date = ?, - modified_date = ?, - modified_by = ? - WHERE container_code = ?""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.container_code,) - - db_connection.write_data(sql_query, values) - - def return_attributes(self) -> tuple: - """Returns the attributes of the Container Object as a tuple. - - Returns: - - tuple: The attributes of the Container. Follows the order of the - columns in the 'containers` table. - """ - - return ( - self.container_id, - self.container_code, - self.container_name, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Container from the database. - - The delete method will delete the Container from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM containers WHERE container_id = ?""" - values = (self.container_id,) - db_connection.write_data(sql_query, values) diff --git a/narcotics_tracker/database.py b/narcotics_tracker/database.py deleted file mode 100644 index ad4e379..0000000 --- a/narcotics_tracker/database.py +++ /dev/null @@ -1,408 +0,0 @@ -"""Defines the database model for the narcotics tracker. - -This module contains the Database Class and its associated methods. All -interactions with the database are done through this module or any -associated modules as needed. - -The Narcotics Tracker makes use of the SQLite3 package which comes bundled -with Python. It requires no additional libraries, services or configuration. -The database is stored in the data/ directory within the root directory of the -project. - -Classes: - Database: Interacts with the SQLite3 database. -""" - - -import os -import sqlite3 - -from narcotics_tracker import ( - containers, - events, - inventory, - medications, - reporting_periods, - statuses, - units, -) -from narcotics_tracker.builders import ( - adjustment_builder, - container_builder, - event_builder, - medication_builder, - reporting_period_builder, - status_builder, - unit_builder, -) - - -def return_datetime(string_date_time: str = None) -> int: - """Returns current local date time as unixepoch formatted integer.""" - with Database("inventory.db") as db: - - sql_query = """SELECT unixepoch();""" - - if string_date_time: - sql_query = f"""SELECT unixepoch('{string_date_time}');""" - - data = db.return_data(sql_query)[0][0] - - return data - - -def format_datetime_from_unixepoch(unix_date_time: int) -> str: - """Formats a unixepoch datetime to readable format.""" - with Database("test_database.db") as db: - - sql_query = """SELECT datetime(?, 'unixepoch', 'localtime');""" - values = [unix_date_time] - - return db.return_data(sql_query, values)[0][0] - - -class Database: - """Interacts directly with the database. - - Initializer: - __init__(self) -> None: - Initializes the database object and sets connection to None. - - Instance Methods: - connect: Creates a connection to the database. - - create_table: Creates a table in the database. - - return_tables: Returns a list of tables as a list. - - return_columns: Returns the column names from a table as a list. - - delete_table: Deletes a table from the database. - - update_table: Updates a table using the ALTER TABLE statement. - - return_data: Returns queried data as a list. - - write_data: Writes data to the database. - - load_medication: Create a medication object from data in the database. - - Static Methods: - - created_date_is_none: Returns True if the created date is None. - - """ - - def __init__(self, filename: str = "inventory.db") -> None: - """Initializes the database object and sets it's connection to None. - - Sets the connection to None. Sets the filename to the passed filename. - - Args: - filename (str): the filename of the database the object will - connect to. - """ - self.connection = None - self.filename = filename - - def __enter__(self) -> "Database": - """Creates a connection to the database and returns the cursor. - - Returns: - sqlite3.cursor: The cursor object which executes sql queries. - """ - try: - self.connection = sqlite3.connect("data/" + self.filename) - except sqlite3.Error as e: - print("Database connection error.") - print(e) - finally: - return self - - def __exit__(self, type, value, traceback) -> None: - """Closes the database connection.""" - self.connection.close() - - def delete_database(self, database_file: str) -> None: - """Deletes a database from the data/ directory. - - Args: - database_file (str): The database file located in the data/ - directory. - """ - try: - os.remove("data/" + database_file) - except Exception as e: - print(e) - - def create_table(self, sql_query: str) -> None: - """Creates a table in the database. - - Args: - sql_query (str): The SQL query to create the table. i.e. - "CREATE TABLE table_name (column_name column_type)" - """ - cursor = self.connection.cursor() - cursor.execute(sql_query) - - def return_table_names(self) -> list: - """Returns a list of tables in the database. - - Returns: - table_list (list): The list of tables in the database. - """ - cursor = self.connection.cursor() - cursor.execute( - """SELECT name FROM sqlite_schema WHERE type='table' ORDER BY name""" - ) - raw_data = cursor.fetchall() - - return [name[0] for name in raw_data] - - def return_columns(self, sql_query: str) -> list: - """Returns the column names from a table as a list. - - Args: - sql_query (str): The SQL query to return the column names. i.e. - 'SELECT * FROM table_name' - """ - cursor = self.connection.cursor() - cursor.execute(sql_query) - return [description[0] for description in cursor.description] - - def delete_table(self, sql_query: str) -> None: - """Deletes a table from the database. - - Args: - sql_query (str): The SQL query to delete the table. i.e. DROP - TABLE IF EXISTS table_name - """ - cursor = self.connection.cursor() - cursor.execute(sql_query) - self.connection.commit() - - def update_table(self, sql_query: str) -> None: - """Updates a table using the ALTER TABLE statement. - - Args: - sql_query (str): The SQL query to update the table. - - Options: - Rename Table: ALTER TABLE table_name RENAME TO new_table_name - Add Column: ALTER TABLE table_name ADD column_name data_type - Rename Column: ALTER TABLE table_name RENAME COLUMN column_name - TO new_column_name - """ - cursor = self.connection.cursor() - cursor.execute(sql_query) - self.connection.commit() - - def return_data(self, sql_query: str, values: list = None) -> list: - """Returns queried data as a list. - - Args: - sql_query (str): The SQL query to read from the database. i.e. - SELECT * FROM table_name - - values (list): The values to be passed to the query. - - Returns: - data (list): The data returned from the query. - """ - cursor = self.connection.cursor() - if values is None: - cursor.execute(sql_query) - else: - cursor.execute(sql_query, values) - return cursor.fetchall() - - def write_data(self, sql_query: str, values: str) -> None: - """Writes data to the database. - - Args: - sql_query (str): The SQL query to write to the database. i.e. - INSERT INTO table_name (column_name) VALUES (value) - - values (list): The values to be passed to the query. - """ - cursor = self.connection.cursor() - cursor.execute(sql_query, values) - self.connection.commit() - - @staticmethod - def created_date_is_none(object) -> bool: - """Returns True if the created date is None. - - Args: - object (Object): The object which is being tested. - - Returns: - bool: Returns True if the created date is None. - """ - if object.created_date is None: - return True - else: - return False - - def load_medication(self, code: str) -> "medications.Medication": - """Create a medication object from data in the database. - - Args: - code (str): The code of the medication to be loaded. - - Returns: - medication (medication.Medication): The medication object. - """ - sql_query = """SELECT * FROM medications WHERE MEDICATION_CODE = ?""" - values = (code,) - - result = self.return_data(sql_query, values) - medication_data = medications.parse_medication_data(result) - - med_builder = medication_builder.MedicationBuilder() - med_builder.assign_all_attributes(medication_data) - loaded_med = med_builder.build() - - return loaded_med - - def load_event(self, event_code: str) -> "events.Event": - """Create an Event object from data in the database. - - Args: - event_code (str): The event_code of the Event to be loaded. - - Returns: - event (event_types.Event): The Event object. - """ - sql_query = """SELECT * FROM events WHERE event_code = ?""" - values = (event_code,) - - result = self.return_data(sql_query, values) - event_data = events.parse_event_data(result) - - e_builder = event_builder.EventBuilder() - e_builder.assign_all_attributes(event_data) - loaded_med = e_builder.build() - - return loaded_med - - def load_reporting_period( - self, period_id: int - ) -> "reporting_periods.ReportingPeriod": - """Create a ReportingPeriod object from data in the database. - - Args: - period_id (int): The numeric identifier of the ReportingPeriod to - be loaded. - - Returns: - loaded_period (reporting_periods.ReportingPeriod): The - ReportingPeriod object. - """ - sql_query = """SELECT * FROM reporting_periods WHERE period_id = ?""" - values = (period_id,) - - result = self.return_data(sql_query, values) - period_data = reporting_periods.parse_reporting_period_data(result) - - period_builder = reporting_period_builder.ReportingPeriodBuilder() - period_builder.assign_all_attributes(period_data) - loaded_period = period_builder.build() - - return loaded_period - - def load_adjustment( - self, adjustment_id: int, db_connection: sqlite3.Connection - ) -> "inventory.Adjustment": - """Create an Adjustment object from data in the database. - - Args: - adjustment_id (int): The numeric identifier of the Adjustment to - be loaded. - - db_connection (sqlite3.Connection): Connection to the database. - - Returns: - loaded_adjustment (inventory.Adjustment): The - Adjustment object. - """ - sql_query = """SELECT * FROM inventory WHERE adjustment_id = ?""" - values = (adjustment_id,) - - result = self.return_data(sql_query, values) - adjustment_data = inventory.parse_adjustment_data(result) - - adj_builder = adjustment_builder.AdjustmentBuilder(db_connection) - adj_builder.assign_all_attributes(adjustment_data) - loaded_adjustment = adj_builder.build() - - return loaded_adjustment - - def load_unit(self, unit_code: str) -> "units.Unit": - """Create an Unit object from data in the database. - - Args: - unit_id (str): The numeric identifier of the Unit to - be loaded. - - Returns: - loaded_unit (units.Unit): The - Unit object. - """ - sql_query = """SELECT * FROM units WHERE unit_code = ?""" - values = (unit_code,) - - result = self.return_data(sql_query, values) - unit_data = units.parse_unit_data(result) - - unt_builder = unit_builder.UnitBuilder() - unt_builder.assign_all_attributes(unit_data) - loaded_unit = unt_builder.build() - - return loaded_unit - - def load_status(self, status_code: str) -> "statuses.status": - """Create an status object from data in the database. - - Args: - status_id (str): The numeric identifier of the status to - be loaded. - - Returns: - loaded_status (statuses.status): The - status object. - """ - sql_query = """SELECT * FROM statuses WHERE status_code = ?""" - values = (status_code,) - - result = self.return_data(sql_query, values) - status_data = statuses.parse_status_data(result) - - unt_builder = status_builder.StatusBuilder() - unt_builder.assign_all_attributes(status_data) - loaded_status = unt_builder.build() - - return loaded_status - - def load_container(self, container_code: str) -> "containers.Container": - """Create a container object from data in the database. - - Args: - container_code (str): The unique identifier of the container to - be loaded. - - Returns: - loaded_container (containers.Container): The - container object. - """ - sql_query = """SELECT * FROM containers WHERE container_code = ?""" - values = (container_code,) - - result = self.return_data(sql_query, values) - container_data = containers.parse_container_data(result) - - cont_builder = container_builder.ContainerBuilder() - cont_builder.assign_all_attributes(container_data) - loaded_container = cont_builder.build() - - return loaded_container diff --git a/narcotics_tracker/events.py b/narcotics_tracker/events.py deleted file mode 100644 index 2373b13..0000000 --- a/narcotics_tracker/events.py +++ /dev/null @@ -1,355 +0,0 @@ -"""Contains the implementation and representation of Events. - -#* Background - -There are numerous ways that controlled substance medications can be added to -or removed from the database. Events help specify why a medication amount was -changed and whether or new the amount was added or removed. All Adjustments -require an event to be specified. - -#* Intended Use - -The module and the Event class defined below allow for the creation of Event -Objects. It is highly recommended to use the Event Builder Module contained -within the Builders Package to create these objects. Instructions for using -builders can be found within that package. - -#* Events in the Database - -Events are stored in the 'events' table of the database with their numeric id, -code, name, description, and creation / modification information specified. -Adjustment objects must specify the event which took place and are limited to -the events listed in the table. - -The Narcotics Tracker comes with a selection of pre-defined events. Refer to -the Standard Items Module inside the Setup Package for more information. - -#* Classes: - - Event: Defines Events and instantiates them as objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the - 'events' table. - - return_events: Returns the 'events' table as lists of strings and values. - - return_operator: Returns an events' operator using it's code. - - parse_event_data: Returns an Event's attributes as a dictionary. -""" -import sqlite3 -from typing import Union - -from narcotics_tracker import database - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'events' table. - - Returns: - - str: The sql query needed to create the 'events' table. - """ - return """CREATE TABLE IF NOT EXISTS events ( - EVENT_ID INTEGER PRIMARY KEY, - EVENT_CODE TEXT UNIQUE, - EVENT_NAME TEXT, - DESCRIPTION TEXT, - OPERATOR INTEGER, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - -def return_events(db_connection: sqlite3.Connection) -> Union[list[str], list]: - """Returns the 'events' table as lists of strings and values. - - Args: - - db_connection (sqlite3.Connection): The database connection. - - Returns: - - events_string_list (list[str]): The contents of the table as a list of - strings. - - events_values_list (list): The contents of the table as a list of - values. - """ - sql_query = """SELECT event_code, event_name, description FROM events""" - - events_strings_list = [] - event_values_list = [] - - events_data = db_connection.return_data(sql_query) - - for event in events_data: - events_strings_list.append(f"Event {event[1]}. Code: {event[0]}. {event[2]}") - event_values_list.append((event[0], event[1], event[2])) - - return events_strings_list, event_values_list - - -def parse_event_data(event_data) -> dict: - """Returns an Event's attributes as a dictionary. - - Args: - - event_data (list): The Event's data. - - Returns: - - attributes (dict): Dictionary object containing the attributes of the - Event. - """ - attributes = {} - - attributes["event_id"] = event_data[0][0] - attributes["event_code"] = event_data[0][1] - attributes["event_name"] = event_data[0][2] - attributes["description"] = event_data[0][3] - attributes["operator"] = event_data[0][4] - attributes["created_date"] = event_data[0][5] - attributes["modified_date"] = event_data[0][6] - attributes["modified_by"] = event_data[0][7] - - return attributes - - -class Event: - """Defines Events and instantiates them as objects. - - This class defines Events within the Narcotics Tracker. Events determine - whether the amount of an adjustment was added or removed from the stock - and what happened to that medication. Inventory Adjustments must specify - the event which occurred and are limited to the items listed in the - 'events' table. - - Events perform one of two operations: they either add or subtract a set - amount of medication from the inventory. This is denoted using the - operator attribute which can be set to +1 or -1 respectively. - - Events can be declared, created and managed using this class. They are - stored in the 'events' table. - - Initializer: - - def __init__(self, builder=None) -> None: - - Initializes an instance of an Event using the builder. - - Attributes: - - event_id (int): Numeric identifier of the Event. Assigned by the - database. - - event_code (str): Unique identifier of the Event. Assigned by the user. - Used to interact with the Event in the database. - - event_name (str): Name of the Event. - - description (str): Description of the Event and when it should be - used. - - operator (int): The operator of the inventory change. '+1' for adding - stock. '-1' for removing stock. Gets multiplied against the - adjustment amounts. - - created_date (str): The date the Event was created in the - table. - - modified_date (str): The date the Event was last modified. - - modified_by (str): Identifier of the user who last modified the - Event. - - Instance Methods: - __repr__: Returns a string expression of the Event Object. - - save: Saves a new Event to the table in the database. - - read: Returns the data of the Event as a tuple. - - update: Updates the Event in the 'events' table. - - return_attributes: Returns the attributes of the Event Object as a - tuple. - - delete: Deletes the Event from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of an Event using the builder. - - Events are complex objects with many attributes. The Builder - Pattern was used to separate the creation of Events to the Builder - Package. - - Refer to the documentation for the EventBuilder Class for more - information. - - Args: - - builder (event_builder.EventBuilder): The builder used - to construct the Event Object. - """ - self.event_id = builder.event_id - self.event_code = builder.event_code - self.event_name = builder.event_name - self.description = builder.description - self.operator = builder.operator - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """Returns a string expression of the Event Object. - - Returns: - - str: The string describing the Event Object. - """ - return ( - f"Event {self.event_name}. Code: {self.event_code}. " f"{self.description}" - ) - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new Event to the table in the database. - - This method will not overwrite a Event already saved in the - database. Use the `update()` to adjust a Event's attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - sql_query = """INSERT OR IGNORE INTO events VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the Event as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the Event's attribute values - in the order of the 'events' table's columns. - """ - sql_query = """SELECT * from events WHERE event_code = ?""" - - values = (self.event_code,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the Event in the 'events' table. - - This method will overwrite the Event's data if it already exists - within the database. An error will be returned if the event_code - does not already exist in the database. Use the save method to save - new Events in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Raises: - - IndexError: An Index Error will be raised if the event_code is not - found on the events table. - - How to use: - - 1. Use the `events.return_events()` method to return a - list of Events. Identify the event_code of the - Event you wish to update. - - 2. Use the database.load_event() method, passing in the - event_code and assigning it to a variable to create a - Event Object. - - 3. Modify the attributes as necessary and call this method on the - Event Object to send the new values to the database. - - #! Note: If the event_id is being changed use the save() - #! method to create a new Event entry in the table and use - #! the delete() method to remove the old entry. - """ - sql_query = """UPDATE events - SET EVENT_ID = ?, - EVENT_CODE = ?, - EVENT_NAME = ?, - DESCRIPTION = ?, - OPERATOR = ?, - CREATED_DATE = ?, - MODIFIED_DATE = ?, - MODIFIED_BY = ? - WHERE EVENT_CODE = ?""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.event_code,) - - db_connection.write_data(sql_query, values) - - def return_attributes(self) -> tuple: - """Returns the attributes of the Event Object as a tuple. - - Returns: - - tuple: The attributes of the Event. Follows the order of the - columns in the 'events` table. - """ - return ( - self.event_id, - self.event_code, - self.event_name, - self.description, - self.operator, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Event from the database. - - The delete method will delete the Event from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM events WHERE event_id = ?""" - values = (self.event_id,) - db_connection.write_data(sql_query, values) diff --git a/narcotics_tracker/inventory.py b/narcotics_tracker/inventory.py deleted file mode 100644 index c1e5a28..0000000 --- a/narcotics_tracker/inventory.py +++ /dev/null @@ -1,389 +0,0 @@ -"""Contains the implementation and representation of Inventory Adjustments. - -#* Background - -The inventory table is the main table used in the Narcotics Tracker. It stores -Adjustments which are the specific events that make changes to the amount of -controlled substances in the inventory, such as administration to a patient or -sending drugs to a reverse distributor for destruction. - -Multiple modules including Event Types, Medication, and the Database module -will interact heavily with this table. - -#* Intended Use - -This module and the Adjustment Class defined below allow for the creation of -Adjustment Objects. It is highly recommended to use the Adjustment Builder -Module contained within the Builders Package to create these objects. -Instructions for using builders can be found within that package. - -#* Adjustments in the Database - -Adjustments are stored in the 'inventory' table of the database with their -numeric id, adjustment date, event code, medication code, amount, -reporting period, reference id, and creation / modification information -specified. Reports will make use of Adjustment's in the table to calculate -medication amount on hand and print out various information for reporting to -oversight organization. - -#* Classes: - - Adjustment: Defines Inventory Adjustments and instantiates them as objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the - 'inventory' table. - - parse_adjustment_data: Returns an Adjustment's attributes as a dictionary. -""" - -import sqlite3 - -from narcotics_tracker import database - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'inventory' table. - - Returns: - - str: The sql query needed to create the 'inventory' table. - """ - return """CREATE TABLE IF NOT EXISTS inventory ( - ADJUSTMENT_ID INTEGER PRIMARY KEY, - ADJUSTMENT_DATE INTEGER, - EVENT_CODE TEXT, - MEDICATION_CODE TEXT, - AMOUNT_IN_MCG REAL, - REPORTING_PERIOD_ID INTEGER, - REFERENCE_ID TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT, - FOREIGN KEY (EVENT_CODE) REFERENCES events (EVENT_CODE) ON UPDATE CASCADE, - FOREIGN KEY (MEDICATION_CODE) REFERENCES medications (MEDICATION_CODE) ON UPDATE CASCADE, - FOREIGN KEY (REPORTING_PERIOD_ID) REFERENCES reporting_periods (PERIOD_ID) ON UPDATE CASCADE - )""" - - -def parse_adjustment_data(adjustment_data) -> dict: - """Returns an Adjustment's attributes as a dictionary. - - Args: - - adjustment_data (list): The Adjustment's data. - - Returns: - - attributes (dict): Dictionary object containing the attributes of the - Adjustment. - """ - attributes = {} - - attributes["adjustment_id"] = adjustment_data[0][0] - attributes["adjustment_date"] = adjustment_data[0][1] - attributes["event_code"] = adjustment_data[0][2] - attributes["medication_code"] = adjustment_data[0][3] - attributes["amount_in_mcg"] = adjustment_data[0][4] - attributes["reporting_period_id"] = adjustment_data[0][5] - attributes["reference_id"] = adjustment_data[0][6] - attributes["created_date"] = adjustment_data[0][7] - attributes["modified_date"] = adjustment_data[0][8] - attributes["modified_by"] = adjustment_data[0][9] - - return attributes - - -class Adjustment: - """Defines Inventory Adjustments and instantiates them as objects. - - This class defines Adjustments within the Narcotics Tracker. Adjustments - are the individual entries which change the amount of medication in the - inventory. - - Adjustments can be declared, created and managed using this class. They - are stored in the 'inventory' table. - - Initializer: - - def __init__(self, builder=None) -> None: - - Initializes an instance of an Adjustment using the builder. - - Attributes: - - adjustment_id (int): The numeric identifier of the Adjustment in the - database. Assigned by the database. - - adjustment_date (int): The date on which the adjustment occurred. - - event_code (str): The unique event_code of the adjustment. - - medication_code (str): The unique medication_code of the medication - which was effected by the adjustment. - - adjustment_amount (float): The amount of medication changed in this - adjustment. - - reference_id (str): The identifier of the reference material which - contains additional information regarding the adjustment. - - created_date (str): The date the Adjustment was created in the - table. - - modified_date (str): The date the Adjustment was last modified. - - modified_by (str): Identifier of the person who last modified the - Adjustment. - - Instance Methods: - - __repr__: Returns a string expression of the Adjustment Object. - - save: Saves a new Adjustment to the table in the database. - - read: Returns the data of the Adjustment as a tuple. - - update: Updates the Adjustment in the 'inventory' table. - - check_and_convert_amount_in_mcg: Checks if the event operator was - changed and updates the amount. - - return_attributes: Returns the attributes of the Adjustment Object as - a tuple. - - delete: Deletes the Adjustment from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of an Adjustment using the builder. - - Adjustments are complex objects with many attributes. The Builder - Pattern was used to separate the creation of Adjustments to the Builder - Package. - - Refer to the documentation for the AdjustmentBuilder Class for more - information. - - Args: - - builder (adjustment_builder.AdjustmentBuilder): The builder used - to construct the Adjustment Object. - """ - self.database_connection = builder.database_connection - self.adjustment_id = builder.adjustment_id - self.adjustment_date = builder.adjustment_date - self.event_code = builder.event_code - self.medication_code = builder.medication_code - self.amount_in_preferred_unit = builder.amount_in_preferred_unit - self.amount_in_mcg = builder.amount_in_mcg - self.reporting_period_id = builder.reporting_period_id - self.reference_id = builder.reference_id - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """Returns a string expression of the Adjustment Object. - - Returns: - - str: The string describing the Adjustment Object. - """ - with database.Database("inventory.db") as db: - - preferred_unit = db.return_data( - """SELECT preferred_unit FROM medications WHERE medication_code = (?)""", - [self.medication_code], - )[0][0] - - medication_name = db.return_data( - """SELECT name FROM medications WHERE medication_code = (?)""", - [self.medication_code], - )[0][0] - - event_name = db.return_data( - """SELECT event_name FROM events WHERE event_code = (?)""", - [self.event_code], - )[0][0] - - return ( - f"Adjustment Number {self.adjustment_id}: " - f"{self.amount_in_preferred_unit} {preferred_unit} of " - f"{medication_name} {event_name} on " - f"{database.format_datetime_from_unixepoch(self.adjustment_date)}." - ) - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new Adjustment to the table in the database. - - This method will not overwrite a Adjustment already saved in the - database. Use the `update()` to adjust a Adjustment's attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - - sql_query = """INSERT OR IGNORE INTO inventory VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the Adjustment as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the Adjustment's attribute values - in the order of the 'inventory' table's columns. - """ - sql_query = """SELECT * from inventory WHERE adjustment_id = ?""" - - values = (self.adjustment_id,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the Adjustment in the 'inventory' table. - - This method will overwrite the Adjustment's data if it already exists - within the database. An error will be returned if the adjustment_id - does not already exist in the database. Use the save method to save - new Adjustments in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Raises: - - IndexError: An Index Error will be raised if the adjustment_code - is not found on the inventory table. - - How to use: - - 1. Use the `inventory.return_adjustments()` method to return a - list of adjustments. Identify the adjustment_id of the - Adjustment you wish to update. - - 2. Use the database.load_adjustment() method, passing in the - adjustment_id and assigning it to a variable to create a - Adjustment Object. - - 3. Modify the attributes as necessary and call this method on the - Adjustment Object to send the new values to the database. - - #! Note: If the adjustment_id is being changed use the save() - #! method to create a new adjustment entry in the table and use - #! the delete() method to remove the old entry. - """ - sql_query = """UPDATE inventory - SET ADJUSTMENT_ID = ?, - ADJUSTMENT_DATE = ?, - EVENT_CODE = ?, - MEDICATION_CODE = ?, - AMOUNT_IN_MCG = ?, - REPORTING_PERIOD_ID = ?, - REFERENCE_ID = ?, - CREATED_DATE = ?, - MODIFIED_DATE = ?, - MODIFIED_BY = ? - WHERE ADJUSTMENT_ID = ?""" - - self.check_and_convert_amount_in_mcg(db_connection) - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.adjustment_id,) - - db_connection.write_data(sql_query, values) - - def check_and_convert_amount_in_mcg( - self, db_connection: sqlite3.Connection - ) -> None: - """Checks if the event operator was changed and updates the amount. - - This method is called when an Adjustment is updated. It pulls the - original operator from the events table, compares it to the new - operator. If they are different the amount is adjusted appropriately. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - old_event_code = db_connection.return_data( - f"""SELECT event_code FROM inventory WHERE adjustment_id='{self.adjustment_id}'""" - )[0][0] - - old_operator = db_connection.return_data( - f"""SELECT operator FROM events WHERE event_code = '{old_event_code}'""" - )[0][0] - - new_operator = db_connection.return_data( - f"""SELECT operator FROM events WHERE event_code = '{self.event_code}'""" - )[0][0] - - if old_operator != new_operator: - self.amount_in_mcg = self.amount_in_mcg * -1 - - def return_attributes(self) -> tuple: - """Returns the attributes of the Adjustment Object as a tuple. - - Returns: - - tuple: The attributes of the Adjustment. Follows the order of the - columns in the 'inventory` table. - """ - return ( - self.adjustment_id, - self.adjustment_date, - self.event_code, - self.medication_code, - self.amount_in_mcg, - self.reporting_period_id, - self.reference_id, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Adjustment from the database. - - The delete method will delete the Adjustment from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM inventory WHERE adjustment_id = ?""" - values = (self.adjustment_id,) - db_connection.write_data(sql_query, values) diff --git a/narcotics_tracker/items/__init__.py b/narcotics_tracker/items/__init__.py new file mode 100644 index 0000000..3c19b2e --- /dev/null +++ b/narcotics_tracker/items/__init__.py @@ -0,0 +1,61 @@ +"""Contains the items which are stored in the database. + +Background + + The classes defined within the modules of this package represent the items + which enable tracking of controlled substance medications. The DataItems + Class is an abstract base class which defines the interface for all other + items. The other modules inherit following attributes: table, id, + created_date, modified_date, and modified_by. Each child class is required + to implement the '__str__' method to provide a description of the item + understandable by the user. + + Medications represent the controlled substances which are being tracked + and contain information required to complete tracking. Different + medications use different units of measurement; These are defined in the + Units class. When the amount of medication in the inventory is changed and + Adjustment is created to reflect the change. Controlled substance activity + must be tracked; The Event class defines the type of events which can + occur and are listed as part of an adjustment. Events also determine + whether they add or remove medication amounts from the inventory. Every 6 + months EMS agencies are required to send reports to the various oversight + agencies. Each six month blocks is recorded as a Reporting Period listing + its start date and end date. Medications, reporting periods and future + items may require a way to track their status. Statuses are defined using + the Status class. + +Intended Use + + Data Items are only responsible for tracking the values assigned to their + attributes. The '__str__' returns a message which the user can use to + quickly identify the item. These items should contain no other methods. + + Data Items contain a large number of attributes, making them difficult to + construct. The Builders Package contains builders for each Data Item and + should be used to created them. Review the documentation for the builders + for more information. + + Python's 'vars' method returns a dictionary mapping the attribute names + with their values. The first key 'table' maps to the name of the SQLite3 + Database table the item lives in. The rest of the keys match the column + names and can be used to construct SQL statements for saving them into the + database. + +Interfaces: + + DataItem: Defines the interface for items stored in the database. + +Modules: + + Adjustments: Defines the changes which occur to the inventory. + + Events: Defines the types of events which can affect the inventory. + + Medications: Defines the medications which are tracked. + + Reporting_Periods: Defines the reporting period for medication tracking. + + Statuses: Defines the statuses for other data items. + + Units: Defines the units of measurements for medications. +""" diff --git a/narcotics_tracker/items/adjustments.py b/narcotics_tracker/items/adjustments.py new file mode 100644 index 0000000..d3a2103 --- /dev/null +++ b/narcotics_tracker/items/adjustments.py @@ -0,0 +1,38 @@ +"""Defines the changes which occur to the inventory. + +Classes: + Adjustment: A change which occurred to the inventory. +""" + +from dataclasses import dataclass + +from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +@dataclass +class Adjustment(DataItem): + """A change which occurred to the inventory. + + Attributes: + adjustment_date (int): Unix timestamp when the adjustment occurred. + + event_code (str): Unique code of the event that caused the adjustment. + + medication_code (str): Unique code of the medication being adjusted. + + amount (float): Amount of medication being adjusted. + + reference_id (str): ID of the document containing more adjustment info. + + reporting_period_id (int): ID of the period adjustment occurred during. + """ + + adjustment_date: int + event_code: str + medication_code: str + amount: float + reference_id: str + reporting_period_id: int + + def __str__(self) -> str: + return f"Adjustment #{self.id}: {self.medication_code} adjusted by {self.amount} due to {self.event_code} on {self.adjustment_date}." diff --git a/narcotics_tracker/items/events.py b/narcotics_tracker/items/events.py new file mode 100644 index 0000000..3a0fa64 --- /dev/null +++ b/narcotics_tracker/items/events.py @@ -0,0 +1,31 @@ +"""Defines the types of events which can affect the inventory. + +Classes: + Event: A type of event which can affect the inventory. +""" +from dataclasses import dataclass + +from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +@dataclass +class Event(DataItem): + """A type of event which can affect the inventory. + + Attributes: + event_code (str): Unique code identifying the event. + + event_name (str): Name of the event. + + description (str): Description of the event. + + modifier (int): (+1 or -1) Specifies if the event adds or removes from the inventory. + """ + + event_code: str + event_name: str + description: str + modifier: int + + def __str__(self) -> str: + return f"Event #{self.id}: {self.event_name} ({self.event_code}) {self.description}" diff --git a/narcotics_tracker/items/interfaces/__init__.py b/narcotics_tracker/items/interfaces/__init__.py new file mode 100644 index 0000000..f671406 --- /dev/null +++ b/narcotics_tracker/items/interfaces/__init__.py @@ -0,0 +1,5 @@ +"""Contains the interfaces for the Items Package. + +Modules: + dataitem_interface: Defines the interface for items stored in the database. +""" diff --git a/narcotics_tracker/items/interfaces/dataitem_interface.py b/narcotics_tracker/items/interfaces/dataitem_interface.py new file mode 100644 index 0000000..dea40ce --- /dev/null +++ b/narcotics_tracker/items/interfaces/dataitem_interface.py @@ -0,0 +1,34 @@ +"""Defines the interface for items stored in the database. + +Classes: + DatabaseItems: The interface for items which are stored in the database. +""" +from abc import ABC, abstractmethod +from dataclasses import dataclass + + +@dataclass +class DataItem(ABC): + """The interface for items which are stored in the database. + + Attributes: + table (str): Name of the table the item belongs to. + + id (int): Numeric identifier of the item. + + created_date (int): Unix timestamp when the item was first added. + + modified_date (int): Unix timestamp when the item was last modified. + + modified_by (str): The name of the user who last modified the item. + """ + + table: str + id: int + created_date: int + modified_date: int + modified_by: str + + @abstractmethod + def __str__(self) -> str: + """Returns a string representation of the item.""" diff --git a/narcotics_tracker/items/medications.py b/narcotics_tracker/items/medications.py new file mode 100644 index 0000000..49cebac --- /dev/null +++ b/narcotics_tracker/items/medications.py @@ -0,0 +1,40 @@ +"""Defines the medications which are tracked. + +Classes: + Medication: A controlled substance medication which is tracked. +""" +from dataclasses import dataclass + +from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +@dataclass +class Medication(DataItem): + """A controlled substance medication which is to be tracked. + + Attributes: + medication_code (str): Unique code identifying the medication. + + medication_name (str): Name of the medication. + + fill_amount (float): Amount of liquid the medication is dissolved in. + + medication_amount (float): Amount of medication. + + preferred_unit (str): The unit of measurement for the medication. + + concentration (float): The ratio of medication to liquid. + + status (str): Status of the medication. + """ + + medication_code: str + medication_name: str + fill_amount: float + medication_amount: float + preferred_unit: str + concentration: float + status: str + + def __str__(self) -> str: + return f"Medication #{self.id}: {self.medication_name} ({self.medication_code}) {self.medication_amount} {self.preferred_unit} in {self.fill_amount} ml." diff --git a/narcotics_tracker/items/reporting_periods.py b/narcotics_tracker/items/reporting_periods.py new file mode 100644 index 0000000..67d08c4 --- /dev/null +++ b/narcotics_tracker/items/reporting_periods.py @@ -0,0 +1,29 @@ +"""Defines the reporting period for medication tracking. + +Classes: + ReportingPeriod: A period of time for medication tracking. +""" +from dataclasses import dataclass + +from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +@dataclass +class ReportingPeriod(DataItem): + """A period of time for medication tracking. + + Attributes: + start_date (int): Unix timestamp of when the reporting period opened. + + end_date (int): Unix timestamp of when the reporting period closed. + + status (str): Status of the reporting period. + + """ + + start_date: int + end_date: int + status: str + + def __str__(self) -> str: + return f"Reporting Period #{self.id}: Start Date: {self.start_date}, End Date: {self.end_date}, Current Status: {self.status}." diff --git a/narcotics_tracker/items/statuses.py b/narcotics_tracker/items/statuses.py new file mode 100644 index 0000000..c147f83 --- /dev/null +++ b/narcotics_tracker/items/statuses.py @@ -0,0 +1,29 @@ +"""Defines the statuses for other data items. + +Classes: + Status: A status for other data items. +""" +from dataclasses import dataclass + +from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +@dataclass +class Status(DataItem): + """A status for other data items. + + Attributes: + status_code (str): Unique identifier for the status. + + status_name (str): Name of the status. + + description (str): Description of the status. + + """ + + status_code: str + status_name: str + description: str + + def __str__(self) -> str: + return f"Status #{self.id}: {self.status_name} ({self.status_code}) {self.description}" diff --git a/narcotics_tracker/items/units.py b/narcotics_tracker/items/units.py new file mode 100644 index 0000000..50f32d6 --- /dev/null +++ b/narcotics_tracker/items/units.py @@ -0,0 +1,28 @@ +"""Defines the units of measurements for medications. + +Classes: + Unit: A unit of measurement for medications. +""" +from dataclasses import dataclass + +from narcotics_tracker.items.interfaces.dataitem_interface import DataItem + + +@dataclass +class Unit(DataItem): + """A unit of measurement for medications. + + Attributes: + unit_code (str): Unique identifier for the unit. + + unit_name (str): Name of the unit. + + decimals (int): Number of decimal places for the unit. + """ + + unit_code: str + unit_name: str + decimals: int + + def __str__(self) -> str: + return f"Unit #{self.id}: {self.unit_name} ({self.unit_code})." diff --git a/narcotics_tracker/medications.py b/narcotics_tracker/medications.py deleted file mode 100644 index 2773e88..0000000 --- a/narcotics_tracker/medications.py +++ /dev/null @@ -1,432 +0,0 @@ -"""Contains the implementation and representation of Medication Objects. - -#* Background - -In order for the Narcotics Tracker to track the inventory of controlled -substances the medications have to be defined and their attributes stored. - -Medications are the back bone of this program and a lot of detail went in -designing medications which can provide the information needed to manage an -agency's inventory. Refer to the Medication Class documentation for a -breakdown of the attributes. - -#* Intended Use - -This module and the Medication Class defined below allow for the creation of -Medication Objects. It is highly recommended to use the Medication Builder -Module contained within the Builders Package to create these objects. -Instructions for using builders can be found within that package. - -#* Medications in the Database - -Medications are stored in the 'medications' table of the database with their -numeric id, code, name, container type, fill amount, dose and unit of -measurement, concentration, status and creation / modification information -specified. Inventory Adjustments must specify which medication were effected -and are limited to the Medications listed in the table. - -#* Classes: - - Medication: Defines Medications and instantiates them as objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the table. - - return_medications: Returns the 'medications' table as lists of strings - and values. - - parse_medication_data: Returns a Medications's attributes as a dictionary. - - return_preferred_unit: Queries the medications table for the preferred - unit of the medication. -""" - - -import sqlite3 -from typing import Union - -from narcotics_tracker import database -from narcotics_tracker.utils import unit_converter - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'medications' table. - - Returns: - - str: The sql query needed to create the 'medications' table. - """ - return """CREATE TABLE IF NOT EXISTS medications ( - MEDICATION_ID INTEGER PRIMARY KEY, - MEDICATION_CODE TEXT UNIQUE, - NAME TEXT, - CONTAINER_TYPE TEXT, - FILL_AMOUNT REAL, - DOSE_IN_MCG REAL, - PREFERRED_UNIT TEXT, - CONCENTRATION REAL, - STATUS TEXT, - CREATED_DATE INT, - MODIFIED_DATE INT, - MODIFIED_BY TEXT, - FOREIGN KEY (PREFERRED_UNIT) REFERENCES units (unit_code) ON UPDATE CASCADE, - FOREIGN KEY (CONTAINER_TYPE) REFERENCES containers (container_code) ON UPDATE CASCADE, - FOREIGN KEY (STATUS) REFERENCES statuses (status_code) ON UPDATE CASCADE - )""" - - -def return_medications(db_connection: sqlite3.Connection) -> Union[list[str], list]: - """Returns the 'medications' table as lists of strings and values. - - Args: - - db_connection (sqlite3.Connection): The database connection. - - Returns: - - medications_string_list (list[str]): The contents of the table as a - list of strings. - - medications_values_list (list): The contents of the table as a list of - values. - """ - sql_query = """SELECT medication_code, name, fill_amount, dose_in_mcg, preferred_unit FROM medications""" - - medications_string_list = [] - medications_values_list = [] - - medications_data = db_connection.return_data(sql_query) - for medication in medications_data: - - if medication[4] == "g": - converted_dose = unit_converter.UnitConverter.to_G(medication[3], "mcg") - if medication[4] == "mg": - converted_dose = unit_converter.UnitConverter.to_mg(medication[3], "mcg") - else: - converted_dose = medication[5] - - medications_string_list.append( - f"{medication[1]} {converted_dose} {medication[4]} in {medication[2]} ml. Code: {medication[0]}." - ) - medications_values_list.append( - (medication[0], medication[1], medication[2], converted_dose, medication[4]) - ) - - return medications_string_list, medications_values_list - - -def parse_medication_data(medication_data) -> dict: - """Returns a Medications's attributes as a dictionary. - - Args: - - medication_data (list): The Medication's data. - - Returns: - - attributes (dict): Dictionary object containing the attributes of the - Medication. - """ - attributes = {} - - attributes["medication_id"] = medication_data[0][0] - attributes["name"] = medication_data[0][2] - attributes["medication_code"] = medication_data[0][1] - attributes["container_type"] = medication_data[0][3] - attributes["fill_amount"] = medication_data[0][4] - attributes["dose"] = medication_data[0][5] - attributes["unit"] = medication_data[0][6] - attributes["concentration"] = medication_data[0][7] - attributes["status"] = medication_data[0][8] - attributes["created_date"] = medication_data[0][9] - attributes["modified_date"] = medication_data[0][10] - attributes["modified_by"] = medication_data[0][11] - - return attributes - - -def return_preferred_unit( - medication_code: str, db_connection: sqlite3.Connection -) -> str: - """Queries the medications table for the preferred unit of the medication. - - Args: - - medication_code (str): The unique identifier for the medication from - the medications table. - - db_connection (sqlite3.Connection): The connection to the database. - - Returns: - - preferred_unit (str): The preferred unit for the medication as a - string. - """ - sql_query = """SELECT preferred_unit FROM medications WHERE medication_code=(?)""" - values = [medication_code] - - preferred_unit = db_connection.return_data(sql_query, values) - - return preferred_unit[0][0] - - -class Medication: - """Defines Medications and instantiates them as objects. - - This class defines Medications within the Narcotics Tracker. Their - attributes are used to calculate the amounts of each Medication in the - inventory. - - Medications can be declared, created and managed using this class. - Adjustments are limited to affecting the Medications stored in the - 'medications' table. - - Initializer: - - def __init__(self, builder=None) -> None: - - Initializes an instance of a Medication using the builder. - - Attributes: - - medication_id (int): The numeric identifier of the Medication in the - database. Assigned by the database. - - code (str): The unique identifier for the specific Medication. - Assigned by the user. Used to interact with the Medication in the - database. - - name (str): The proper name of the Medication. - - container_type (str): The type of container the - Medication comes in. Limited by the items stored in the - 'containers' table. - - fill_amount (float): The amount of the solvent in the container. - Measured in milliliters (ml). - - dose (float): The amount of Medication in the container. - - preferred_unit (str): The unit of measurement the Medication is - commonly measured in. Limited to the items stored in the 'units' - table. - - concentration (float): The concentration of the Medication to its - solvent. - - status (str): The status of the Medication. Limited to the items - stored in the 'statuses' table. - - created_date (str): The date the Medication was created in the - table. - - modified_date (str): The date the Medication was last modified. - - modified_by (str): Identifier of the person who last modified the - Medication. - - Instance Methods: - - __repr__: Returns a string expression of the Medication object. - - save: Saves a new Medication to the table in the database. - - read: Returns the data of the Medication as a tuple. - - update: Updates the Medication in the 'medications' table. - - return_attributes: Returns the attributes of the Medication Object as - a tuple. - - delete: Deletes the Medication from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of a Medication using the builder. - - Medications are complex objects with many attributes. The Builder - Pattern was used to separate the creation of Medication to the Builder - Package. - - Refer to the documentation for the MedicationBuilder Class for more - information. - - Args: - - builder (medication_builder.MedicationBuilder): The builder used - to construct the Medication Object. - """ - - self.medication_id = builder.medication_id - self.medication_code = builder.medication_code - self.name = builder.name - self.container_type = builder.container_type - self.fill_amount = builder.fill_amount - self.dose = builder.dose - self.preferred_unit = builder.unit - self.concentration = builder.concentration - self.status = builder.status - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """Returns a string expression of the Medication Object. - - Returns: - - str: The string describing the Medication Object. - """ - - return ( - f"{self.name} {self.dose}{self.preferred_unit} in " - f"{self.fill_amount}ml. Code: {self.medication_code}." - ) - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new Medication to the table in the database. - - This method will not overwrite a Medication already saved in the - database. Use the `update()` to adjust a Medication's attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - - sql_query = """INSERT OR IGNORE INTO medications VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the Medication as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the Medication's attribute values - in the order of the 'medications' table's columns. - """ - sql_query = """SELECT * from medications WHERE medication_code = ?""" - - values = (self.medication_code,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the Medication in the 'medications' table. - - This method will overwrite the Medication's data if it already exists - within the database. An error will be returned if the medication_code - does not already exist in the database. Use the save method to save - new Medications in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Raises: - - IndexError: An Index Error will be raised if the medication_code - is not found on the medications table. - - How to use: - - 1. Use the `medications.return_medications()` method to return a - list of medications. Identify the medication_code of the - medication you wish to update. - - 2. Use the database.load_medication() method, passing in the - medication_code and assigning it to a variable to create a - Medication Object. - - 3. Modify the attributes as necessary and call this method on the - Medication Object to send the new values to the database. - - #! Note: If the medication_code is being changed use the save() - #! method to create a new medication entry in the table and use - #! the delete() method to remove the old entry. - """ - sql_query = """UPDATE medications - SET MEDICATION_ID = ?, - MEDICATION_CODE = ?, - NAME = ?, - CONTAINER_TYPE = ?, - FILL_AMOUNT = ?, - DOSE_IN_MCG = ?, - PREFERRED_UNIT = ?, - CONCENTRATION = ?, - STATUS = ?, - CREATED_DATE = ?, - MODIFIED_DATE = ?, - MODIFIED_BY = ? - WHERE MEDICATION_CODE = ?""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.medication_code,) - - db_connection.write_data(sql_query, values) - - def return_attributes(self) -> tuple: - """Returns the attributes of the Medication Object as a tuple. - - Returns: - - tuple: The attributes of the Medication. Follows the order of the - columns in the 'medications' table. - """ - return ( - self.medication_id, - self.medication_code, - self.name, - self.container_type, - self.fill_amount, - self.dose, - self.preferred_unit, - self.concentration, - self.status, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Medication from the database. - - The delete method will delete the Medication from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM medications WHERE medication_id = ?""" - values = (self.medication_id,) - db_connection.write_data(sql_query, values) diff --git a/narcotics_tracker/reporting_periods.py b/narcotics_tracker/reporting_periods.py deleted file mode 100644 index 1209be9..0000000 --- a/narcotics_tracker/reporting_periods.py +++ /dev/null @@ -1,344 +0,0 @@ -"""Contains the implementation and representation of Reporting Period Objects. - -#* Background - -EMS agencies need to report all changes to their controlled substance -inventory periodically. Grouping Inventory Adjustments in to reporting periods -keeps them organized and is helpful when generating reports. - -#* Intended Use - -This module and the ReportingPeriods Class defined below allow for the -creation of Reporting Period Objects. It is highly recommended to use the -Reporting Period Builder Module contained within the Builders Package to -create these objects. Instructions for using builders can be found within that -package. - -#* Reporting Periods in the Database - -Reporting Periods are stored in the 'reporting_periods' table of the database -with their numeric id, starting date, ending date, and creation / modification -information specified. Inventory Adjustments will be assigned to a Reporting -Period based on the date on which they occurred and are limited to the -Reporting Periods listed in the table. - -#* Classes: - - ReportingPeriod: Defines Reporting Periods and instantiates them as - objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the - 'reporting_periods' table. - - return_periods: Returns the 'reporting_periods' table as lists of strings - and values. - - parse_reporting_period_data: Returns a Reporting Periods's attributes as a - dictionary. -""" - -import sqlite3 -from typing import Union - -from narcotics_tracker import database - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'reporting_periods' table. - - Returns: - - str: The sql query needed to create the 'reporting_periods' table. - """ - return """CREATE TABLE IF NOT EXISTS reporting_periods ( - PERIOD_ID INTEGER PRIMARY KEY, - STARTING_DATE INTEGER, - ENDING_DATE INTEGER, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - -def return_periods(db_connection: sqlite3.Connection) -> Union[list[str], list]: - """Returns the 'reporting_periods' table as lists of strings and values. - - Args: - - db_connection (sqlite3.Connection): The database connection. - - Returns: - - period_string_list (list[str]): The contents of the table as a list of - strings. - - period_values_list (list): The contents of the table as a list of - values. - """ - sql_query = ( - """SELECT period_id, starting_date, ending_date FROM reporting_periods""" - ) - - periods_string_list = [] - periods_values_list = [] - - periods_data = db_connection.return_data(sql_query) - - for period in periods_data: - periods_string_list.append( - f"Reporting Period {period[0]}. Started on: {database.format_datetime_from_unixepoch(period[1])}. Ends on: {database.format_datetime_from_unixepoch(period[2])}" - ) - periods_values_list.append((period[0], period[1], period[2])) - return periods_string_list, periods_values_list - - -def parse_reporting_period_data(reporting_period_data) -> dict: - """Returns a Reporting Periods's attributes as a dictionary. - - Args: - - reporting_period_data (list): The Reporting Period's data. - - Returns: - - attributes (dict): Dictionary object containing the attributes of the - Reporting Period. - """ - attributes = {} - - attributes["period_id"] = reporting_period_data[0][0] - attributes["starting_date"] = reporting_period_data[0][1] - attributes["ending_date"] = reporting_period_data[0][2] - attributes["created_date"] = reporting_period_data[0][3] - attributes["modified_date"] = reporting_period_data[0][4] - attributes["modified_by"] = reporting_period_data[0][5] - - return attributes - - -class ReportingPeriod: - """Defines Reporting Periods and instantiates them as objects. - - This class defines Reporting Periods within the Narcotics Tracker. - Reporting Periods are to organize Inventory Adjustments base on the date - they occurred. - - Reporting Periods can be declared, created and managed using this class. - Adjustments are limited to using the Reporting Periods stored in the - 'reporting_periods' table. - - Attributes: - - period_id (int): Numeric identifier of each Reporting Period. - Assigned by the database. Used to interact with the Reporting - Period in the database. - - starting_date (str): The date when the Reporting Period starts. - - ending_date (str): The date when the Reporting Period ends. - - created_date (str): The date the Reporting Period was created in the - table. - - modified_date (str): The date the Reporting Period was last modified. - - modified_by (str): Identifier of the person who last modified the - Reporting Period. - - Initializer: - def __init__(self, starting_date: str, ending_date: str) -> None: - - Initializes an instance of a Reporting Period using the - ReportingPeriodBuilder. - - Instance Methods: - __repr__: Returns a string expression of the Reporting Period object. - - save: Saves a new Reporting Period to the table in the database. - - read: Returns the data of the Reporting Period as a tuple. - - update: Updates the Reporting Period in the 'reporting_periods' table. - - return_attributes:Returns the attributes of the Reporting Period - Object as a tuple. - - delete: Deletes the Reporting Period from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of a Reporting Period using the builder. - - Reporting Periods are complex objects with many attributes. The - Builder Pattern was used to separate the creation of Reporting Periods - to the Builder Package. - - Refer to the documentation for the ReportingPeriodBuilder Class for - more information. - - Args: - - builder (reporting_period_builder.ReportingPeriodBuilder: The - builder used to construct the Reporting Period Object. - """ - self.period_id = builder.period_id - self.starting_date = builder.starting_date - self.ending_date = builder.ending_date - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """Returns a string expression of the reporting period Object. - - Returns: - str: The string describing the reporting period Object - """ - - starting_date = database.format_datetime_from_unixepoch(self.starting_date) - ending_date = database.format_datetime_from_unixepoch(self.ending_date) - - return ( - f"Reporting Period {self.period_id}. Started on: " - f"{starting_date}. Ends on: {ending_date}." - ) - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new Reporting Period to the table in the database. - - This method will not overwrite a Reporting Period already saved in the - database. Use the `update()` to adjust a Reporting Period's - attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - sql_query = """INSERT OR IGNORE INTO reporting_periods VALUES ( - ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the Reporting Period as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the Reporting Period's attribute values - in the order of the 'reporting_periods' table's columns. - """ - sql_query = """SELECT * from reporting_periods WHERE period_id = ?""" - - values = (self.period_id,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the Reporting Period in the 'reporting_periods' table. - - This method will overwrite the Reporting Period's data if it already - exists within the database. An error will be returned if the period_id - does not already exist in the database. Use the save method to save - new Reporting Periods in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Raises: - - IndexError: An Index Error will be raised if the period_id is not - found on the reporting_periods table. - - How to use: - - 1. Use the `reporting_periods.return_periods()` method to return a - list of Reporting Periods. Identify the period_id of the period - you wish to update. - - 2. Use the database.load_reporting_period() method, passing in the - period_id and assigning it to a variable to create a Reporting - Periods Object. - - 3. Modify the attributes as necessary and call this method on the - Reporting Period Object to send the new values to the - database. - - #! Note: If the period_id is being changed use the save() method - #! to create a new reporting period entry in the table and use the - #! delete() method to remove the old entry. - """ - sql_query = """UPDATE reporting_periods - SET PERIOD_ID = ?, - STARTING_DATE = ?, - PERIOD_NAME = ?, - ENDING_DATE = ?, - CREATED_DATE = ?, - MODIFIED_DATE = ?, - MODIFIED_BY = ? - WHERE PERIOD_ID = ?""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.period_id,) - - db_connection.write_data(sql_query, values) - - def return_attributes(self) -> tuple: - """Returns the attributes of the Reporting Period Object as a tuple. - - Returns: - - tuple: The attributes of the Reporting Period. Follows the order - of the columns in the 'reporting_periods' table. - """ - - return ( - self.period_id, - self.starting_date, - self.ending_date, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Reporting Period from the database. - - The delete method will delete the Reporting Period from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM reporting_periods WHERE period_id = ?""" - values = (self.period_id,) - db_connection.write_data(sql_query, values) diff --git a/scripts/__init__.py b/narcotics_tracker/scripts/__init__.py similarity index 64% rename from scripts/__init__.py rename to narcotics_tracker/scripts/__init__.py index 3697069..b559ce0 100644 --- a/scripts/__init__.py +++ b/narcotics_tracker/scripts/__init__.py @@ -5,5 +5,7 @@ create_my_database: Creates the medications which I use at my agency and writes them to the table. - setup: This script sets up the Narcotics Tracker. + setup: Sets up the Narcotics Tracker. + + wlvac_adjustment: Adds all inventory adjustments to the WLVAC Inventory. """ diff --git a/narcotics_tracker/scripts/create_my_database.py b/narcotics_tracker/scripts/create_my_database.py new file mode 100644 index 0000000..a35097f --- /dev/null +++ b/narcotics_tracker/scripts/create_my_database.py @@ -0,0 +1,174 @@ +"""Script which works with a personal database for testing.""" + +from typing import TYPE_CHECKING + +from narcotics_tracker import commands +from narcotics_tracker.builders.medication_builder import MedicationBuilder +from narcotics_tracker.builders.reporting_period_builder import ReportingPeriodBuilder +from narcotics_tracker.configuration.standard_items import StandardItemCreator +from narcotics_tracker.scripts import setup +from narcotics_tracker.services.datetime_manager import DateTimeManager +from narcotics_tracker.services.sqlite_manager import SQLiteManager + +if TYPE_CHECKING: + from narcotics_tracker.items.medications import Medication + from narcotics_tracker.items.reporting_periods import ReportingPeriod + + +dt_man = DateTimeManager() + + +def main(): + """Sets up the narcotics database for WLVAC.""" + # * Initialize objects and Create Database File. + sq_man = SQLiteManager("inventory_wlvac.db") + std_creator = StandardItemCreator() + + # * Add Tables. + # setup.create_tables(sq_man, setup.return_tables_list()) + + # * Populate Database with Standard Items. + events = std_creator.create_events() + setup.populate_events(sq_man, events) + + statuses = std_creator.create_statuses() + setup.populate_statuses(sq_man, statuses) + + units = std_creator.create_units() + setup.populate_units(sq_man, units) + + # * Populate Database with WLVAC Medications. + # meds = build_wlvac_meds() + + # for medication in meds: + # commands.AddMedication(sq_man, medication).execute() + + # * Populate Database with Reporting Periods for 2022. + # periods = build_reporting_periods(dt_man) + + # for period in periods: + # commands.AddReportingPeriod(sq_man, period).execute() + + +def build_wlvac_meds() -> list["Medication"]: + """Builds Medication Objects used at WLVAC and returns them as a list.""" + wlvac_medications = [] + med_builder = MedicationBuilder() + + fentanyl = ( + med_builder.set_medication_code("fentanyl") + .set_medication_name("Fentanyl") + .set_fill_amount(2) + .set_medication_amount(100) + .set_preferred_unit("mcg") + .set_concentration() + .set_status("ACTIVE") + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + midazolam = ( + med_builder.set_medication_code("midazolam") + .set_medication_name("Midazolam") + .set_fill_amount(2) + .set_medication_amount(1000) + .set_preferred_unit("mg") + .set_concentration(5) + .set_status("ACTIVE") + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + morphine = ( + med_builder.set_medication_code("morphine") + .set_medication_name("Morphine") + .set_fill_amount(1) + .set_medication_amount(1000) + .set_preferred_unit("mg") + .set_concentration(10) + .set_status("ACTIVE") + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + + wlvac_medications.append(fentanyl) + wlvac_medications.append(morphine) + wlvac_medications.append(midazolam) + + return wlvac_medications + + +def build_reporting_periods( + dt_manager: DateTimeManager, +) -> list["ReportingPeriod"]: + """Builds Reporting Period Objects for 2022 and returns them as a list.""" + periods = [] + + period_builder = ReportingPeriodBuilder() + + jan_to_june_2021 = ( + period_builder.set_start_date( + dt_manager.convert_to_timestamp("01-01-2021 00:00:00") + ) + .set_end_date(dt_manager.convert_to_timestamp("06-30-2021 23:59:59")) + .set_status("CLOSED") + .set_id(2100000) + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + + july_to_december_2021 = ( + period_builder.set_start_date( + dt_manager.convert_to_timestamp("07-01-2021 00:00:00") + ) + .set_end_date(dt_manager.convert_to_timestamp("12-31-2021 23:59:59")) + .set_status("CLOSED") + .set_id() + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + + jan_to_june_2022 = ( + period_builder.set_start_date( + dt_manager.convert_to_timestamp("01-20-2022 00:00:00") + ) + .set_end_date(dt_manager.convert_to_timestamp("07-22-2022 23:59:59")) + .set_status("CLOSED") + .set_id(2200000) + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + + july_to_december_2022 = ( + period_builder.set_start_date( + dt_manager.convert_to_timestamp("07-23-2022 00:00:00") + ) + .set_end_date(None) + .set_status("OPEN") + .set_id() + .set_created_date(dt_man.return_current()) + .set_modified_date(dt_man.return_current()) + .set_modified_by("SRK") + .build() + ) + + periods.append(jan_to_june_2021) + periods.append(july_to_december_2021) + periods.append(jan_to_june_2022) + periods.append(july_to_december_2022) + + return periods + + +if __name__ == "__main__": + main() diff --git a/narcotics_tracker/scripts/setup.py b/narcotics_tracker/scripts/setup.py new file mode 100644 index 0000000..8293377 --- /dev/null +++ b/narcotics_tracker/scripts/setup.py @@ -0,0 +1,135 @@ +"""Sets up the Narcotics Tracker. + +This script is intended to be called called the first time the Narcotics +Tracker is being used. It will created the database, tabes, and standard +items. + +Functions: + + main: Creates a database file, populates with the standard tables and + items. + + clear_screen: Clears the screen. + + create_tables: Initializes the database and sets up the tables. + + populate_events: Adds the Standard Events to the database. + + populate_statuses: Adds the Standard Statuses to the database. + + populate_units: Adds the Standard Units to the database. +""" + +import os +import sqlite3 +from typing import TYPE_CHECKING + +from narcotics_tracker import commands +from narcotics_tracker.configuration.standard_items import StandardItemCreator +from narcotics_tracker.services.service_manager import ServiceManager + +if TYPE_CHECKING: + from narcotics_tracker.commands.interfaces.command import Command + + +def main() -> None: + """Creates a database file, populates with the standard tables and items.""" + clear_screen() + + print("Welcome to the Narcotics Tracker!\n") + print("Starting database setup.\n") + + print("Preparing to create tables:") + create_tables() + print("\nTable creation complete!!\n") + + print("Preparing to add standard items:\n") + populate_events() + populate_statuses() + populate_units() + + print("\nStandard items added successfully.") + print("\nNarcotics Tracker database setup complete.") + input("Press ENTER to continue.") + + +def clear_screen(): + """Clears the screen.""" + clear = "cls" if os.name == "nt" else "clear" + os.system(clear) + + +def create_tables() -> str: + """Initializes the database and sets up the tables.""" + persistence_manager = ServiceManager().persistence + commands = _return_table_list() + + for command in commands: + command().execute() + print(f"- {command.table_name} table created.") + + +def _return_table_list() -> list["Command"]: + """Returns a list of table creation commands.""" + tables_list = [ + commands.CreateEventsTable, + commands.CreateInventoryTable, + commands.CreateMedicationsTable, + commands.CreateReportingPeriodsTable, + commands.CreateStatusesTable, + commands.CreateUnitsTable, + ] + return tables_list + + +def populate_events() -> None: + """Adds the Standard Events to the database.""" + events = StandardItemCreator().create_events() + counter = 0 + + for event in events: + try: + commands.AddEvent().execute(event) + except sqlite3.IntegrityError as e: # Events likely in the database already. + pass + else: + counter += 1 + + print(f"- {counter} events added to the database.") + + +def populate_statuses() -> None: + """Adds the Standard Statuses to the database.""" + statuses = StandardItemCreator().create_statuses() + counter = 0 + + for status in statuses: + try: + commands.AddStatus().execute(status) + except sqlite3.IntegrityError as e: # Statuses likely in the database already. + pass + else: + counter += 1 + + print(f"- {counter} statuses added to the database.") + + +def populate_units() -> None: + """Adds the Standard Units to the database.""" + units = StandardItemCreator().create_units() + counter = 0 + + for unit in units: + try: + commands.AddUnit().execute(unit) + except sqlite3.IntegrityError as e: # Units likely in the database already. + print(e) + pass + else: + counter += 1 + + print(f"- {counter} units added to the database.") + + +if __name__ == "__main__": + main() diff --git a/narcotics_tracker/scripts/wlvac_adjustment.py b/narcotics_tracker/scripts/wlvac_adjustment.py new file mode 100644 index 0000000..f4fd713 --- /dev/null +++ b/narcotics_tracker/scripts/wlvac_adjustment.py @@ -0,0 +1,128 @@ +"""Adds all inventory adjustments to the WLVAC Inventory.""" + +from narcotics_tracker import commands +from narcotics_tracker.builders.adjustment_builder import AdjustmentBuilder +from narcotics_tracker.items.adjustments import Adjustment +from narcotics_tracker.services.datetime_manager import DateTimeManager +from narcotics_tracker.services.service_manager import ServiceManager +from narcotics_tracker.services.sqlite_manager import SQLiteManager + +dt_man = DateTimeManager() +sq_man = SQLiteManager("inventory_wlvac.db") + + +def construct_adjustments(data: list[any]) -> list["Adjustment"]: + adjustment_list = [] + for data_set in data: + adjustment = Adjustment( + table="inventory", + id=data_set[0], + created_date=dt_man.return_current(), + modified_date=dt_man.return_current(), + modified_by="SRK", + adjustment_date=data_set[1], + event_code=data_set[2], + medication_code=data_set[3], + amount=data_set[4], + reference_id=data_set[6], + reporting_period_id=data_set[5], + ) + adjustment_list.append(adjustment) + + return adjustment_list + + +adjustment = Adjustment( + table="inventory", + id=None, + created_date=dt_man.return_current(), + modified_date=dt_man.return_current(), + modified_by="SRK", + adjustment_date=dt_man.convert_to_timestamp("07-22-2022 17:00:00"), + event_code="IMPORT", + medication_code="fentanyl", + amount=7450, + reference_id="Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", + reporting_period_id=2200000, +) + +midazolam_june_2022 = Adjustment( + table="inventory", + id=None, + created_date=dt_man.return_current(), + modified_date=dt_man.return_current(), + modified_by="SRK", + adjustment_date=dt_man.convert_to_timestamp("07-22-2022 17:00:00"), + event_code="IMPORT", + medication_code="midazolam", + amount=265360.0, + reference_id="Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", + reporting_period_id=2200000, +) + +morphine_june_2022 = Adjustment( + table="inventory", + id=None, + created_date=dt_man.return_current(), + modified_date=dt_man.return_current(), + modified_by="SRK", + adjustment_date=dt_man.convert_to_timestamp("07-22-2022 17:00:00"), + event_code="IMPORT", + medication_code="morphine", + amount=690000, + reference_id="Narcotics Bi-Annual Report - June 2022 - Wantagh-Levittown VAC", + reporting_period_id=2200000, +) + + +def main() -> None: + + service_provider = ServiceManager(repository="inventory_wlvac.db") + sq, dt, converter = service_provider.start_services() + + adjustment_data = [] + + use_1 = [None, 1659212760, "USE", "fentanyl", -50, 2200000, "PCR# 220830"] + adjustment_data.append(use_1) + des_1 = [ + None, + 1661027838, + "DESTRUCTION", + "morphine", + -440000, + 2200000, + "RxRD# 37265", + ] + adjustment_data.append(des_1) + des_2 = [ + None, + 1661027838, + "DESTRUCTION", + "midazolam", + -363400, + 2200000, + "RxRD# 37265", + ] + adjustment_data.append(des_2) + des_3 = [None, 1661027838, "DESTRUCTION", "fentanyl", -3450, 2200000, "RxRD# 37265"] + adjustment_data.append(des_3) + use_2 = [None, 1661166388, "USE", "midazolam", -5000, 2200000, "PCR# 220920"] + adjustment_data.append(use_2) + use_3 = [None, 1661701387, "USE", "fentanyl", -60, 2200000, "PCR# 220945"] + adjustment_data.append(use_3) + use_4 = [None, 1662580020, "USE", "fentanyl", -100, 2200000, "PCR# 220976"] + adjustment_data.append(use_4) + use_5 = [None, 1665258240, "USE", "midazolam", -5000, 2200000, "PCR# 221095"] + adjustment_data.append(use_5) + use_6 = [None, 1666487700, "USE", "midazolam", -1600, 2200000, "PCR# 221144"] + adjustment_data.append(use_6) + + adjustment_list = construct_adjustments(adjustment_data) + + for adjustment in adjustment_list: + message = commands.AddAdjustment(sq, adjustment).execute() + print(message) + + +if __name__ == "__main__": + main() diff --git a/narcotics_tracker/services/__init__.py b/narcotics_tracker/services/__init__.py new file mode 100644 index 0000000..c936c3d --- /dev/null +++ b/narcotics_tracker/services/__init__.py @@ -0,0 +1,53 @@ +"""Contains the services which are used by the Narcotics Tracker. + +The Narcotics Tracker makes use of various utilities which have been +implemented within the modules in this package. + +These services are made available through the ServiceManager which provides +instances of each service to the other modules and classes which need them. +Additional services, or replacement services, should be added to the +ServiceManager and made available using a method named for the type of service +provided. + +Packages: + interfaces: Organizes the interfaces for utility services within the + Narcotics Tracker. + +Modules: + service_manager: Provides access to the services used by the Narcotics + Tracker. + + conversion_manager: Handles conversion between different units. + + datetime_manager: Handles datetime functions for the Narcotics Tracker. + + sqlite_manager: Manages Communication with the SQLite3 Database. + +Accessing Services: + When a service is needed they can be instantiated by accessing the + appropriate property in the ServiceProvider. + + Example: + + ```python + datetime_service = ServiceProvider().datetime + + persistence_service = ServiceProvider().persistence + + conversion_service = ServiceProvider().conversion + ``` + +Changing The Database: + The ServiceProvider's database property stores the filename of the + database file as a string. It can be changed by specifying a different + file using that property. + + Example: + + ```python + services = ServiceProvider() + + services.database = 'new_filename.db' + + persistence_service = services.persistence +""" diff --git a/narcotics_tracker/services/conversion_manager.py b/narcotics_tracker/services/conversion_manager.py new file mode 100644 index 0000000..25e2ae8 --- /dev/null +++ b/narcotics_tracker/services/conversion_manager.py @@ -0,0 +1,91 @@ +"""Handles conversion between different units. + +Controlled substance medications are measured in different units. + +The Preferred Unit is the unit that the medication is commonly measured in. + +The Standard Unit is a measurement of mass stored as an integer in the data +repository. The Standard Unit allows for precision to be preserved to a +minimum of two decimal places regardless of the preferred unit. + +Reports sent to the NYS Department of Health and Bureau of Narcotic +Enforcement require medication amounts to be converted to volume. The +medication's concentration enables conversion this conversion. Volumes are +always reported in milliliters. + +Classes: + UnitConverter: Converts between different units of measurement. +""" + + +from typing import Union + +from narcotics_tracker.services.interfaces.conversion import ConversionService + + +class ConversionManager(ConversionService): + """Converts between different units of measurement. + + Methods: + + to_standard: Returns an amount of medication in the standard unit. + + to_preferred: Returns an amount of medication in its preferred unit. + + to_milliliters: Returns the volume of a medication (in ml) using its + concentration. + """ + + _decimals = {"std": -8, "mcg": -6, "mg": -3, "g": 0} + + def to_standard(self, amount: Union[int, float], preferred_unit: str) -> float: + """Returns an amount of medication in the standard unit. + + Args: + amount (int / float): Amount of medication in its preferred + unit. + + preferred_unit (str): The unit_code of the medication's + preferred unit. Valid unit_codes: 'mcg', 'mg', 'g', 'std'. + + Returns: + float: The converted amount. + """ + exponent = self._decimals[preferred_unit] - self._decimals["std"] + + return amount * (10**exponent) + + def to_preferred(self, amount: Union[int, float], preferred_unit: str) -> float: + """Returns an amount of medication in its preferred unit. + + Args: + amount (int / float): Amount of medication in the standard unit. + + preferred_unit (str): The unit_code of the medication's preferred + unit. Valid unit_codes: 'mcg', 'mg', 'g', 'std'. + + Returns: + float: The amount of the medication in it's preferred unit. + """ + exponent = self._decimals["std"] - self._decimals[preferred_unit] + + return amount * (10**exponent) + + def to_milliliters( + self, amount: Union[int, float], preferred_unit: str, concentration: float + ) -> float: + """Returns the volume of a medication (in ml) using its concentration. + + Args: + amount (int / float): Amount of medication in the standard unit. + + preferred_unit (str): The medication's preferred unit of mass. + + concentration (float): The medication's concentration. + + Returns: + float: The volume of the medication in milliliters. + """ + adjusted_amount = self.to_preferred(amount, preferred_unit) + + return adjusted_amount / concentration diff --git a/narcotics_tracker/services/datetime_manager.py b/narcotics_tracker/services/datetime_manager.py new file mode 100644 index 0000000..67f4576 --- /dev/null +++ b/narcotics_tracker/services/datetime_manager.py @@ -0,0 +1,82 @@ +"""Handles datetime functions for the Narcotics Tracker. + +Classes: + DateTimeManager: Provides date and time services. +""" + +from typing import Union + +import pendulum + +from narcotics_tracker.services.interfaces.datetime import DateTimeService + + +class DateTimeManager(DateTimeService): + """Provides date and time services. + + Methods: + + return_current: Returns the current datetime as a timestamp. + + convert_to_timestamp: Returns a formatted string (MM-DD-YYYY HH:MM:SS) + as a timestamp. + + convert_to_string: Returns a timestamp as a readable string. + + validate: Corrects invalid dates and returns them as a unix timestamp. + """ + + def __init__(self, dt_pkg: object = pendulum, tz: str = "America/New_York") -> None: + """Assigns the datetime package, and timezone. + + dt_package (object, optional): Datetime package used. Defaults to + pendulum. + + tz (str, optional): Timezone using the IANA Timezone Database. + Defaults to America/New York. + """ + self._timezone = tz + self._datetime_package = dt_pkg + + def return_current(self) -> int: + """Returns the current datetime as a timestamp.""" + return self._current_datetime().int_timestamp + + def convert_to_timestamp(self, string_datetime: str) -> int: + """Returns a formatted string (MM-DD-YYYY HH:MM:SS) as a timestamp.""" + format = "MM-DD-YYYY HH:mm:ss" + dt = self._datetime_package.from_format(string_datetime, format, self._timezone) + + return dt.int_timestamp + + def convert_to_string(self, timestamp: int) -> str: + """Returns a timestamp as a readable string.""" + dt_object = self._datetime_package.from_timestamp(timestamp, self._timezone) + + return dt_object.format("MM-DD-YYYY HH:mm:ss") + + def validate(self, date: Union[int, str]) -> int: + """Corrects invalid dates and returns them as a unix timestamp.""" + if self._date_is_invalid(date): + return self._assign(date) + + return date + + def _date_is_invalid(self, date: Union[int, str]) -> bool: + """Returns False if the date is a timestamp, otherwise returns True.""" + return True if (date is None or type(date) is str) else False + + def _current_datetime(self): + """Returns current datetime as a timestamp, accounts for the timezone.""" + return self._datetime_package.now(tz=self._timezone) + + def _assign(self, date: Union[int, str] = None) -> int: + """Returns the correct datetime depending on the date valued passed.""" + if date is None: + return self.return_current() + + if type(date) is str: + return self.convert_to_timestamp(date) + + if type(date) is int: + return date diff --git a/narcotics_tracker/services/interfaces/__init__.py b/narcotics_tracker/services/interfaces/__init__.py new file mode 100644 index 0000000..611f126 --- /dev/null +++ b/narcotics_tracker/services/interfaces/__init__.py @@ -0,0 +1,12 @@ +"""Organizes the interfaces for utility services within the Narcotics Tracker. + +Modules: + conversion: Defines the protocol for unit conversion services. + + datetime: Defines the protocol for working with datetimes. + + persistence: Defines the protocol for communication with data repositories. + + service: Defines the protocol for providing services within the narcotics + tracker. +""" diff --git a/narcotics_tracker/services/interfaces/conversion.py b/narcotics_tracker/services/interfaces/conversion.py new file mode 100644 index 0000000..e6ff644 --- /dev/null +++ b/narcotics_tracker/services/interfaces/conversion.py @@ -0,0 +1,44 @@ +"""Defines the protocol for unit conversion services. + + Classes: + ConversionService: Protocol for unit converters. +""" + +from typing import Protocol + + +class ConversionService(Protocol): + """Protocol for unit converters. + + Classes using this protocol must be able to convert between the different + units of measurement using the three methods declared below. Each method + returns a float which is accepted by the database table. + + Unit converters are used to convert between the preferred unit of + measurement stored in the medication table, the standard unit which is + used for all medication amounts within the database. Unit converters are + also responsible for converting a medication amount in mass to it's + equivalent measurement in milliliters (volume). Bi-annual reports to the + Department of Health and Bureau of Narcotics Enforcement require + medication amounts in milliliters. This conversion is generally done using + the concentration value stored in the medication's data. + + Review the documentation of the Units Package for more information on the + various units used in the narcotics tracker. + + Methods: + to_standard: Returns an amount of medication in the standard unit. + + to_preferred: Returns an amount of medication in the preferred unit. + + to_milliliters: Returns an amount of medication in milliliters. + """ + + def to_standard() -> float: + ... + + def to_preferred() -> float: + ... + + def to_milliliters() -> float: + ... diff --git a/narcotics_tracker/services/interfaces/datetime.py b/narcotics_tracker/services/interfaces/datetime.py new file mode 100644 index 0000000..d19a883 --- /dev/null +++ b/narcotics_tracker/services/interfaces/datetime.py @@ -0,0 +1,41 @@ +"""Defines the protocol for working with datetimes. + +Classes: + DatetimeService: Protocol for working with datetimes. +""" + +from typing import Protocol + + +class DateTimeService(Protocol): + """Protocol for working with datetimes. + + Classes using this protocol must be able provide the current datetime as a + unix timestamp, convert between a formatted string (MM-DD-YYYY HH:ss:mm) + and the unix timestamp. DateTimeService providers are also used to + evaluate user entered dates and format them correctly. + + Datetimes are stored in the database as a unix timestamp. They are input + by and presented to the user as a formatted string: 'MM-DD-YYYY HH:MM:SS'. + + Methods: + return_current: Returns the current datetime as a unix timestamp. + + convert_to_timestamp: Converts a formatted string to a timestamp. + + convert_to_string: Converts a timestamp to the formatted string. + + validate: Checks a date and converts it as necessary. + """ + + def return_current() -> int: + ... + + def convert_to_timestamp() -> int: + ... + + def convert_to_string() -> str: + ... + + def validate() -> int: + ... diff --git a/narcotics_tracker/services/interfaces/persistence.py b/narcotics_tracker/services/interfaces/persistence.py new file mode 100644 index 0000000..9503721 --- /dev/null +++ b/narcotics_tracker/services/interfaces/persistence.py @@ -0,0 +1,36 @@ +"""Defines the protocol for communication with data repositories. + +Classes: + PersistenceService: Protocol for communicating with a data repository. + """ + +from typing import Protocol + + +class PersistenceService(Protocol): + """Protocol for communicating with a data repository. + + Classes using this protocol must be able to store and retrieve data from + the data repository using the methods declared below. + + Methods: + add: Adds new data to the repository. + + remove: Deletes data from the repository. + + read: Returns data from the repository. + + update: Updates data in the repository. + """ + + def add(): + ... + + def remove(): + ... + + def read(): + ... + + def update(): + ... diff --git a/narcotics_tracker/services/interfaces/service_provider.py b/narcotics_tracker/services/interfaces/service_provider.py new file mode 100644 index 0000000..119a5c8 --- /dev/null +++ b/narcotics_tracker/services/interfaces/service_provider.py @@ -0,0 +1,45 @@ +"""Defines the protocol for providing services within the narcotics tracker. + +Classes: + ServiceProvider: Protocol for providing services. + +""" + + +from typing import TYPE_CHECKING, Protocol + +if TYPE_CHECKING: + from narcotics_tracker.services.interfaces.conversion import ConversionService + from narcotics_tracker.services.interfaces.datetime import DateTimeService + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class ServiceProvider(Protocol): + """Protocol for providing services. + + ServiceProviders are intended to offer a simple way to provide the various + utilities used by the Narcotics Tracker while allowing flexibility to add + or change objects which provide the functionality. + + Classes using this protocol must be able to store the services and return + instances of them using the methods and attributes declared below. + + Methods: + persistence: Returns an instance of the object which communicates with + the data repository. + + datetime: + Returns an instance of the object which handles datetimes. + + conversion: Returns an instance of the object which handles unit + conversion. + """ + + def persistence() -> "PersistenceService": + ... + + def datetime() -> "DateTimeService": + ... + + def conversion() -> "ConversionService": + ... diff --git a/narcotics_tracker/services/service_manager.py b/narcotics_tracker/services/service_manager.py new file mode 100644 index 0000000..a4ba606 --- /dev/null +++ b/narcotics_tracker/services/service_manager.py @@ -0,0 +1,68 @@ +from typing import TYPE_CHECKING + +from narcotics_tracker.services.conversion_manager import ConversionManager +from narcotics_tracker.services.datetime_manager import DateTimeManager +from narcotics_tracker.services.interfaces.service_provider import ServiceProvider +from narcotics_tracker.services.sqlite_manager import SQLiteManager + +if TYPE_CHECKING: + from narcotics_tracker.services.interfaces.conversion import ConversionService + from narcotics_tracker.services.interfaces.datetime import DateTimeService + from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class ServiceManager(ServiceProvider): + """Provides access to the services used by the Narcotics Tracker. + + Properties: + persistence: Assigns and returns an instance of the persistence + service. + + database: Assigns and returns the filename of the database file if + used. + + datetime: Assigns and returns an instance of the datetime service. + + conversion: Returns an instance of the conversion service. + """ + + _persistence: "PersistenceService" = SQLiteManager + _database: str = "inventory.db" + _datetime: "DateTimeService" = DateTimeManager + _conversion: "ConversionService" = ConversionManager + + @property + def persistence(self) -> "PersistenceService": + """Returns an instance of the persistence service.""" + return self._persistence(self._database) + + @persistence.setter + def persistence(self, value: "PersistenceService"): + self._persistence = value + + @property + def database(self) -> str: + """Assigns and returns the filename of the database file if used.""" + return self._database + + @database.setter + def database(self, value: str): + self._database = value + + @property + def datetime(self) -> "DateTimeService": + """Returns an instance of the datetime service.""" + return self._datetime() + + @datetime.setter + def datetime(self, value: "DateTimeService"): + self._datetime = value + + @property + def conversion(self) -> "ConversionService": + """Returns an instance of the conversion service.""" + return self._conversion() + + @conversion.setter + def conversion(self, value: "ConversionService"): + self._conversion = value diff --git a/narcotics_tracker/services/sqlite_manager.py b/narcotics_tracker/services/sqlite_manager.py new file mode 100644 index 0000000..2f1c730 --- /dev/null +++ b/narcotics_tracker/services/sqlite_manager.py @@ -0,0 +1,202 @@ +"""Manages Communication with the SQLite3 Database. + +All information about controlled substance medications and their activities +are stored within an SQLite3 database. This module contains the objects +responsible for communicating with the database. + +Classes: + SQLiteManager: Sends and receives information from the SQlite database. +""" + +import os +import sqlite3 + +from narcotics_tracker.services.interfaces.persistence import PersistenceService + + +class SQLiteManager(PersistenceService): + """Sends and receives information from the SQlite database. + + Attributes: + connection (sqlite3.Connection): The connection to the SQlite database. + + filename (str): The name of the database file. + + Methods: + add: Adds a new row to the database. + + read: Returns a cursor containing data from the database. + + update: Updates a row in the database. + + remove: Removes a row from the database. + + create_table: Adds a table to the database. + + delete_database: Deletes the database file. + """ + + def __init__(self, filename: str) -> None: + """Initialize the SQLiteManager and stores the database filename. + + If the database files doe not exist, it will be created. + + Args: + filename (str): The filename of the database file. + """ + self.connection = sqlite3.connect("data/" + filename) + self.filename = filename + + def __del__(self) -> None: + """Closes the database connection upon exiting the context manager.""" + self.connection.close() + + def add(self, table_name: str, data: dict[str]): + """Adds a new row to the database. + + Args: + table_name (str): Name of the table receiving the new row. + + data (dict[str]): A dictionary mapping column names to the values. + """ + placeholders = ", ".join("?" for key in data.keys()) + column_names = ", ".join(data.keys()) + + sql_statement = ( + f"""INSERT INTO {table_name} ({column_names}) VALUES ({placeholders});""" + ) + + column_values = tuple(data.values()) + + self._execute(sql_statement, column_values) + + def read( + self, table_name: str, criteria: dict[str] = {}, order_by: str = None + ) -> sqlite3.Cursor: + """Returns a cursor containing data from the database. + + Args: + table_name (str): The name of the table. + + criteria (dict[str], optional): A dictionary mapping column names + to values used to select rows from which to pull the data. + + order_by (str, optional): The name of the column by which to order + the data. + + Returns: + sqlite3.Cursor: A cursor contains the returned data. + """ + sql_query = f"""SELECT * FROM {table_name}""" + + if criteria: + placeholders = [f"{column} = ?" for column in criteria.keys()] + criteria_columns = " AND ".join(placeholders) + sql_query += f" WHERE {criteria_columns}" + + if order_by: + sql_query += f" ORDER BY {order_by}" + + return self._execute(sql_query, tuple(criteria.values())) + + def update(self, table_name: str, data: dict[str], criteria: dict[str]) -> None: + """Updates a row in the database. + + Args: + table_name (str): The name of the table. + + data (dict[str]): New data as a dictionary mapping column names to + updated values. + + criteria (dict[str]): A dictionary mapping column names to values + used to select which row to update. + """ + sql_statement = f"""UPDATE {table_name} SET """ + + data_placeholders = ", ".join([f"{column} = ?" for column in data.keys()]) + criteria_placeholders = [f"{column} = ?" for column in criteria.keys()] + criteria_columns = " AND ".join(criteria_placeholders) + + sql_statement += f"{data_placeholders} WHERE {criteria_columns};" + + values = [item for item in data.values()] + for item in criteria.values(): + values.append(item) + + values = tuple(values) + + self._execute(sql_statement, values) + + def remove(self, table_name: str, criteria: dict[str]): + """Removes a row from the database. + + Args: + table_name (str): Name of the table where the row is to be removed. + + criteria (dict[str]): A dictionary mapping column names to values + used to select rows for deletion. + """ + placeholders = [f"{column} = ?" for column in criteria.keys()] + criteria_columns = " AND ".join(placeholders) + + sql_statement = f"""DELETE FROM {table_name} WHERE {criteria_columns};""" + + criteria_values = tuple(criteria.values()) + + self._execute(sql_statement, criteria_values) + + def create_table( + self, + table_name: str, + column_info: dict[str], + foreign_key_info: list[str] = None, + ) -> None: + """Adds a table to the database. + + Does nothing if the table already exists. + + Args: + table_name (str): The name of the table. + column_info (dict[str]): A dictionary mapping column names to its + datatype and restraints. + foreign_key_info (list[str], optional): A list of strings + containing foreign key constraints. + """ + columns_with_details = [ + f" {column_name} {details}" for column_name, details in column_info.items() + ] + + columns_with_details = ", ".join(columns_with_details) + + if foreign_key_info: + foreign_key_constraints = ", ".join(foreign_key_info) + columns_with_details += f", {foreign_key_constraints}" + + sql_statement = ( + f"""CREATE TABLE IF NOT EXISTS {table_name} ({columns_with_details});""" + ) + + self._execute(sql_statement) + + def _execute(self, sql_statement: str, values: tuple[str] = None) -> sqlite3.Cursor: + """Executes the sql statement, returns a cursor with any results. + + Args: + sql_statement (str): The SQL statement to be executed. + values (tuple[str], optional): Any value required to execute the + sql statement. + """ + with self.connection: + cursor = self.connection.cursor() + cursor.execute(sql_statement, values or []) + + return cursor + + def delete_database(self) -> None: + """Deletes the database file.""" + os.remove(f"data/{self.filename}") + self.connection.close() + + def _connect(self) -> None: + """Connects to the database file.""" + self.connection = sqlite3.connect("data/" + self.filename) diff --git a/narcotics_tracker/setup/__init__.py b/narcotics_tracker/setup/__init__.py deleted file mode 100644 index 7445215..0000000 --- a/narcotics_tracker/setup/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Contains the modules needed to set up the Narcotics Tracker software. - -Modules: - - standard_items: Contains a library of pre-defined database items for the - Narcotics Tracker. - -""" diff --git a/narcotics_tracker/setup/standard_items.py b/narcotics_tracker/setup/standard_items.py deleted file mode 100644 index 1375544..0000000 --- a/narcotics_tracker/setup/standard_items.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Contains a library of pre-defined database items for the Narcotics Tracker. - -The builders and modules allow for the creation of new items to be saved into -the database. The items defined in this module are used as part of the -setup.py script to populate the database with the default items. - -Classes: - - None - -Functions: - - None - -Items: - - Events: - - Common events which adjust the amount of medication in the inventory. - - Import Event - - Order Event - - Use Event - - Waste Event - - Destroy Event - - Loss Event - - Units: - - Common units which controlled substance medications are measured in. - - Micrograms (mcg) - - Milligrams (mg) - - Grams (g) - - Milliliters (ml) - - Containers: - - Common containers that controlled substance medications come in. - - Vial - - Pre-filled Syringe - - Pre-mixed Bag - - Statuses: - - Common statuses used for controlled substance medications and other - database items. - - Active (ACTIVE) - - Inactive (INACTIVE) - - Open (OPEN) - - Cancelled (CANCELLED) - - Closed (CLOSED) -""" -STANDARD_EVENTS = [ - { - "event_id": None, - "event_code": "IMPORT", - "event_name": "imported", - "description": "Used when adding pre-existing stock to the table.", - "operator": +1, - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "event_id": None, - "event_code": "ORDER", - "event_name": "ordered", - "description": "Used when adding new stock from a purchase order.", - "operator": +1, - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "event_id": None, - "event_code": "USE", - "event_name": "used", - "description": "Used when subtracting medication that was administered to a patient.", - "operator": -1, - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "event_id": None, - "event_code": "WASTE", - "event_name": "wasted", - "description": "Used when subtracting medication which was wasted.", - "operator": -1, - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "event_id": None, - "event_code": "DESTROY", - "event_name": "destroyed", - "description": "Used when subtracting medication which was destroyed through a reverse distributor.", - "operator": -1, - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "event_id": None, - "event_code": "LOSS", - "event_name": "lost", - "description": "Used when subtracting medication which were lost or stolen.", - "operator": -1, - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, -] - -STANDARD_UNITS = [ - { - "unit_id": None, - "unit_code": "mcg", - "unit_name": "Micrograms", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "unit_id": None, - "unit_code": "mg", - "unit_name": "Milligrams", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "unit_id": None, - "unit_code": "g", - "unit_name": "Grams", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "unit_id": None, - "unit_code": "ml", - "unit_name": "Milliliters", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, -] - -STANDARD_CONTAINERS = [ - { - "container_id": None, - "container_code": "vial", - "container_name": "Vial", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "container_id": None, - "container_code": "pfs", - "container_name": "Pre-filled Syringe", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "container_id": None, - "container_code": "pmb", - "container_name": "Pre-mixed Bag", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, -] - -STANDARD_STATUSES = [ - { - "status_id": None, - "status_code": "ACTIVE", - "status_name": "Active", - "description": "Used for items which are still being used.", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "status_id": None, - "status_code": "INACTIVE", - "status_name": "Inactive", - "description": "Used for items which are no longer being used.", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "status_id": None, - "status_code": "OPEN", - "status_name": "Open", - "description": "Used for orders which have not been completed.", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "status_id": None, - "status_code": "CANCELLED", - "status_name": "Cancelled", - "description": "Used for orders which have been cancelled.", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, - { - "status_id": None, - "status_code": "CLOSED", - "status_name": "Closed", - "description": "Used for orders which have been completed.", - "created_date": None, - "modified_date": None, - "modified_by": "Setup", - }, -] diff --git a/narcotics_tracker/statuses.py b/narcotics_tracker/statuses.py deleted file mode 100644 index adfea91..0000000 --- a/narcotics_tracker/statuses.py +++ /dev/null @@ -1,349 +0,0 @@ -"""Contains the implementation and representation of Object Statuses. - -#* Background - -It is likely that information in the inventory will be changed over time. -Statuses were added to the Narcotics Tracker to record those changes and -present users with information on items which are no longer being used, or are -waiting for an update. An EMS agency may switch from one concentration of a -controlled substance medication to a different one, or track a purchase order -of medications through various stages. Statuses will assist the user in -keeping track of these changes. - -#* Intended Use - -This module and the Status Class defined below allow for the creation of -Status Objects. It is highly recommended to use the Status Builder Module -contained within the Builders Package to create these objects. Instructions -for using builders can be found within that package. - -#* Statuses in the Database - -Statuses are stored in the 'statuses' table of the database with their numeric -ID, code, name, description and creation / modification information specified. -Medication objects must specify their status and are limited to the statuses -listed in the table. - -The Narcotics Tracker comes with a selection of pre-defined statuses. Refer to -the Standard Items Module inside the Setup Package for more information. - -#* Classes: - - Status: Defines statuses and instantiates them as objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the - 'statuses' table. - - return_status: Returns the contents of the statuses table as lists of - strings and values. - - parse_status_data: Returns a Status's attributes from the database as a - dictionary. -""" - -import sqlite3 -from typing import Union - -from narcotics_tracker import database - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'statuses' table. - - Returns: - - str: The sql query needed to create the 'statuses' table. - """ - return """CREATE TABLE IF NOT EXISTS statuses ( - STATUS_ID INTEGER PRIMARY KEY, - STATUS_CODE TEXT UNIQUE, - STATUS_NAME TEXT, - DESCRIPTION TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - -def return_statuses(db_connection: sqlite3.Connection) -> Union[list[str], list]: - """Returns the 'statuses' table as lists of strings and values. - - Args: - - db_connection (sqlite3.Connection): The database connection. - - Returns: - - status_string_list (list[str]): The contents of the table as a list of - strings. - - status_values_list (list): The contents of the table as a list of - values. - """ - sql_query = ( - """SELECT status_id, status_code, status_name, description FROM statuses""" - ) - - status_string_list = [] - status_values_list = [] - - status_data = db_connection.return_data(sql_query) - - for status in status_data: - status_string_list.append( - f"Status {status[0]}: {status[2]}. Code: '{status[1]}'. {status[3]}" - ) - status_values_list.append((status[0], status[1], status[2], status[3])) - - return status_string_list, status_values_list - - -def parse_status_data(status_data) -> dict: - """Returns a Status's attributes from the database as a dictionary. - - Args: - - status_data (list): The Status' data. - - Returns: - - attributes (dict): Dictionary object containing the attributes of the - status. - """ - attributes = {} - - attributes["status_id"] = status_data[0][0] - attributes["status_code"] = status_data[0][1] - attributes["status_name"] = status_data[0][2] - attributes["description"] = status_data[0][3] - attributes["created_date"] = status_data[0][4] - attributes["modified_date"] = status_data[0][5] - attributes["modified_by"] = status_data[0][6] - - return attributes - - -class Status: - """Defines Statuses and instantiates them as objects. - - This class defines Statuses within the Narcotics Tracker. Statuses are - used by various objects to denote their current condition. - - Statuses can be declared, created and managed using this class. - Database items are limited to using the status stored in the 'statuses' - table. - - Attributes: - - status_id (int): Numeric identifier of each Status. Assigned by the - database. - - status_code (str): Unique identifier of each unit type. Assigned by the - user. Used to interact with the unit in the database. - - status_code (str): Name of the unit. - - description (str): A string describing the status and how it should - be used. - - created_date (str): The date the unit type was created in the - table. - - modified_date (str): The date the unit type was last modified. - - modified_by (str): Identifier of the user who last modified the - unit type. - - Initializer: - - def __init__(self, builder=None) -> None: - - Initializes an instance of a Status using the StatusBuilder. - - Instance Methods: - __repr__: Returns a string expression of the Status. - - save: Saves a new Status to the 'statuses' table in the database. - - read: Returns the data of the Status from the database as a tuple. - - update: Updates the Status in the 'statuses' table of the database. - - return_attributes: Returns the attributes of the Status Object as a - tuple. - - delete: Deletes the Status from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of a Status using the StatusBuilder. - - Statuses are complex objects with many attributes. The Builder - Pattern was used to separate the creation of status to the - Builder Package. - - Refer to the documentation for the StatusBuilder Class for more - information. - - Args: - - builder (status_builder.StatusBuilder): The builder used to - construct the Status object. - """ - self.status_id = builder.status_id - self.status_code = builder.status_code - self.status_name = builder.status_name - self.description = builder.description - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """ - - Returns:Returns a string expression of the Status. - - str: The string describing the Status. - """ - return f"Status {self.status_id}: {self.status_name}. Code: '{self.status_code}'. {self.description}" - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new Status to the 'statuses' table in the database. - - This method will not overwrite a Status already saved in the database. - Use the `update()` to adjust a Status's attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - sql_query = """INSERT OR IGNORE INTO statuses VALUES ( - ?, ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the Status from the database as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the Status's attribute values in the - order of the table's columns. - """ - sql_query = """SELECT * from statuses WHERE status_code = ?""" - - values = (self.status_code,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the Status in the 'statuses' table of the database. - - This method will overwrite the Status's data if it already exists within - the database. An error will be returned if the Status_id does not - already exist in the database. Use the save method to save new - statuses in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - status_code (str): The unique identifier of the Status. - - Raises: - - IndexError: An Index Error will be raised if the status_code is not - found on the statuses table. - - How to use: - - 1. Use the `statuses.return_status()` method to return a list of - statuses. Identify the status_code of the status you wish to - update. - - 2. Use the database.load_status() method, passing in the - status_code and assigning it to a variable to create a Status - Object. - - 3. Modify the attributes as necessary and call this method on the - Status Object to send the new values to the database. - - #! Note: If the status_code is being changed use the save() method - #! to create a new status entry in the table and use the delete() - #! method to remove the old entry. - """ - sql_query = """UPDATE statuses - SET status_id = ?, - status_code = ?, - status_name = ?, - description = ?, - created_date = ?, - modified_date = ?, - modified_by = ? - WHERE status_code = ?""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.status_code,) - - db_connection.write_data(sql_query, values) - - def return_attributes(self) -> tuple: - """Returns the attributes of the Status Object as a tuple. - - Returns: - - tuple: The attributes of the Status. Follows the order - of the columns in the 'statuses' table. - """ - - return ( - self.status_id, - self.status_code, - self.status_name, - self.description, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Status from the database. - - The delete method will delete the Status from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM statuses WHERE status_id = ?""" - values = (self.status_id,) - db_connection.write_data(sql_query, values) diff --git a/narcotics_tracker/units.py b/narcotics_tracker/units.py deleted file mode 100644 index 6fa79ef..0000000 --- a/narcotics_tracker/units.py +++ /dev/null @@ -1,374 +0,0 @@ -"""Contains implementation and representation of Units of Measurement. - -#* Background - -Controlled substance medications are suspensions in which the medication -itself (the solute) is dissolved within a liquid (the solvent). Tracking -controlled substance medications requires the use of three types of -measurement: - -The Dose measures the amount of the medication itself and is denoted in metric -units of mass: Grams (g), milligrams (mg), and micrograms (mcg). These units -can be easily converted between. - - #! Note: 1 Gram = 1,000 milligrams = 1,000,000 micrograms. - -The Fill measures the amount of the liquid in which the medication is -suspended in. Fill is denoted in metric units of volume: Liters (L) and -milliliters (ml). - -The Concentration or 'strength' of a medication is calculated by dividing its -mass by the volume of the liquid it's dissolved in. Concentration is used to -calculate the total amount of a controlled substance when reporting to New -York State which requires the volume of the medications to be specified. - -#* Preferred vs. Standard Units - -The Narcotics Tracker divides Dosage Units into two categories. The Preferred -Unit is the unit of measurement by which the controlled substance is generally -referred and dosed by. To simplify math and standardize the inventory tracking -of controlled substance medications all Dosage Amounts are converted into -micrograms before being stored in the database. Medication amounts are -converted back into their preferred unit when being shown to the users. - -#* Intended Use - -This module and the Unit Class defined below allow for the creation of Unit -objects which represent the units of measurement within the Narcotics Tracker. -It is highly recommended to use the Unit Builder Module contained within the -Builders package to create these objects. Instructions for using builders can -be found within that package. - -The Unit Converter Module in the Utils Package performs calculations and -conversions between the different units of measurement. Instructions for using -the Unit Converter can be found within that module. - - #! Note: As of the current version the Unit Converter can only convert - #! between Grams, milligrams, and micrograms. This will be reviewed in a - #! later update. - -#* Units in the Database - -Units are stored within the 'units' table of the database with their numeric -ID, name, code, and creation / modification information specified. Medication -objects must specify their preferred unit and are limited to the units listed -in the units table. - -The Narcotics Tracker comes with a selection of pre-defined units. Refer to -the Standard Items Module inside the Setup Package for more information. - -#* Classes: - - Unit: Defines units and instantiates them as objects. - -#* Functions: - - return_table_creation_query: Returns the query needed to create the - 'units' table. - - return_units: Returns the contents of the units table as lists of strings - and values. - - parse_unit_data: Returns a Unit's attributes from the database as a - dictionary. -""" - -import sqlite3 -from typing import Union - -from narcotics_tracker import database - - -def return_table_creation_query() -> str: - """Returns the query needed to create the 'units' table. - - Returns: - - str: The sql query needed to create the 'units' Table. - """ - return """CREATE TABLE IF NOT EXISTS units ( - UNIT_ID INTEGER PRIMARY KEY, - UNIT_CODE TEXT UNIQUE, - UNIT_NAME TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - -def return_units(db_connection: sqlite3.Connection) -> Union[list[str], list]: - """Returns the contents of the units table as lists of strings and values. - - Args: - - db_connection (sqlite3.Connection): The database connection. - - Returns: - - units_string_list (list): The Units in the table as a list of strings. - - units_values_list (list ): The Units in the table as a list of values. - """ - sql_query = """SELECT unit_id, unit_code, unit_name FROM units""" - - units_string_list = [] - units_values_list = [] - - periods_data = db_connection.return_data(sql_query) - - for period in periods_data: - units_string_list.append( - f"Unit Number {period[0]}: {period[2]}. Code: '{period[1]}'." - ) - units_values_list.append((period[0], period[1], period[2])) - return units_string_list, units_values_list - - -def parse_unit_data(unit_data) -> dict: - """Returns a Unit's attributes from the database as a dictionary. - - This function is used to pass a Unit's data into the database.load_unit() - method. - - Args: - - unit_data (list): The unit's raw data. Obtained using - database.return_data(). - - Returns: - - attributes (dict): Dictionary objects contains the attributes of - the unit. - """ - attributes = {} - - attributes["unit_id"] = unit_data[0][0] - attributes["unit_code"] = unit_data[0][1] - attributes["unit_name"] = unit_data[0][2] - attributes["created_date"] = unit_data[0][3] - attributes["modified_date"] = unit_data[0][4] - attributes["modified_by"] = unit_data[0][5] - - return attributes - - -class Unit: - """Defines Units and instantiates them as objects. - - This class defines Units of Measurements within the Narcotics Tracker. - Units are used by the Medication objects to denote how amounts of that - medication should be represented to the user and to convert the amount - into the standard unit. - - Units can be declared, created and managed using this class. Medications - are limited to using the units stored in the units table. - - Attributes: - - unit_id (int): Numeric identifier of each unit. Assigned by the - database. - - unit_code (str): Unique identifier of each unit type. Assigned by the - user. Used to interact with the unit in the database. - - - unit_name (str): Proper name of the unit. - - created_date (str): The date on which the unit was first created in - the units table. - - modified_date (str): The date on which the unit was last modified in - the database. - - modified_by (str): Identifier of the user who last modified the - unit in the database. - - Initializer: - - def __init__(self, builder=None) -> None: - - Initializes an instance of an Unit using the UnitBuilder. - - Instance Methods: - - __repr__: Returns a string expression of the unit. - - save: Saves a new unit to the units table in the database. - - read: Returns the data of the unit from the database as a tuple. - - update: Updates the unit in the units table of the - database. - - return_attributes: Returns the attributes of the units object as a - tuple. - - delete: Deletes the unit from the database. - """ - - def __init__(self, builder=None) -> None: - """Initializes an instance of an Unit using the UnitBuilder. - - Units are complex objects with many attributes. The Builder - Pattern is used to separate the creation of Units to the - Builder Package. - - Refer to the documentation for the UnitBuilder Class for more - information. - - Args: - - builder (unit_builder.UnitBuilder): The builder used to - construct the Unit object. - """ - self.unit_id = builder.unit_id - self.unit_code = builder.unit_code - self.unit_name = builder.unit_name - self.created_date = builder.created_date - self.modified_date = builder.modified_date - self.modified_by = builder.modified_by - - def __repr__(self) -> str: - """Returns a string expression of the Unit. - - Returns: - - str: The string describing the Unit. - """ - return ( - f"Unit Number {self.unit_id}: {self.unit_name}. Code: '{self.unit_code}'." - ) - - def save(self, db_connection: sqlite3.Connection) -> None: - """Saves a new unit to the units table in the database. - - This method will not overwrite a Unit already saved in the database. - Use the `update()` to adjust a Unit's attributes. - - Assigns a created_date and modified_date. - - Args: - - db_connection (sqlite3.Connection): The database connection. - """ - sql_query = """INSERT OR IGNORE INTO units VALUES ( - ?, ?, ?, ?, ?, ?)""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() - - db_connection.write_data(sql_query, values) - - def read(self, db_connection: sqlite3.Connection) -> tuple: - """Returns the data of the unit from the database as a tuple. - - This method makes no changes to the data. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - Returns: - - tuple: A tuple containing the unit's attribute values in the - order of the table's columns. - """ - sql_query = """SELECT * from units WHERE unit_code = ?""" - - values = (self.unit_code,) - - data = db_connection.return_data(sql_query, values) - - return data - - def update(self, db_connection: sqlite3.Connection) -> None: - """Updates the unit in the units table of the database. - - This method will overwrite the Unit's data if it already exists within - the database. An error will be returned if the unit_id does not - already exist in the database. Use the save method to save new units - in the database. - - Assigns a new modified_date. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - - unit_code (str): The unique identifier of the unit. - - Raises: - - IndexError: An Index Error will be raised if the unit_code is not - found on the units table. - - How to use: - - 1. Use the `units.return_units()` method to return a list of - units. Identify the unit_code of the unit you wish to update. - - 2. Use the database.load_unit() method, passing in the unit_code - and assigning it to a variable to create a Unit Object. - - 3. Modify the attributes as necessary and call this method on the - Unit Object to send the new values to the database. - - #! Note: If the unit_code is being changed use the save() method - #! to create a new unit entry in the table and use the delete - #! method to remove the old entry. - """ - sql_query = """UPDATE units - SET unit_id = ?, - unit_code = ?, - unit_name = ?, - created_date = ?, - modified_date = ?, - modified_by = ? - WHERE unit_code = ?""" - - if database.Database.created_date_is_none(self): - self.created_date = database.return_datetime() - self.modified_date = database.return_datetime() - - values = self.return_attributes() + (self.unit_code,) - - db_connection.write_data(sql_query, values) - - def return_attributes(self) -> tuple: - """Returns the attributes of the Units object as a tuple. - - Returns: - - tuple: The attributes of the Units. Follows the order - of the columns in the units table. - """ - return ( - self.unit_id, - self.unit_code, - self.unit_name, - self.created_date, - self.modified_date, - self.modified_by, - ) - - def delete(self, db_connection: sqlite3.Connection) -> None: - """Deletes the Unit from the database. - - The delete method will delete the Unit from the database - entirely. - - #! Note: Deleting an item from the database is irreversible. - - Args: - - db_connection (sqlite3.Connection): The connection to the - database. - """ - sql_query = """DELETE FROM units WHERE unit_id = ?""" - values = (self.unit_id,) - db_connection.write_data(sql_query, values) diff --git a/narcotics_tracker/utils/__init__.py b/narcotics_tracker/utils/__init__.py deleted file mode 100644 index d30f645..0000000 --- a/narcotics_tracker/utils/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""The Utilities Package contains modules and functions that assist with -controlled substance inventory management. - -Modules: - unit_converter: Assists in converting medications between different units - of measurement. -""" diff --git a/narcotics_tracker/utils/unit_converter.py b/narcotics_tracker/utils/unit_converter.py deleted file mode 100644 index 8beb65c..0000000 --- a/narcotics_tracker/utils/unit_converter.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Assists in converting medications between different units of measurement. - -Classes: - UnitConverter: Converts between different units of measurement. -""" - - -class UnitConverter: - """Converts between different units of measurement. - - Methods: - to_mcg: Converts from a specified unit to micrograms. - to_mg: Converts from a specified unit to milligrams. - to_G: Converts from a specified unit to grams. - """ - - def to_mcg(amount: float, starting_unit: str) -> float: - """Converts from a specified unit to micrograms. - - Args: - amount (float): The amount to convert. - - unit - - Returns: - amount (float): The amount converted to micrograms. - """ - if starting_unit == "mg": - return amount * 10**3 - - elif starting_unit == "g": - return amount * 10**6 - - elif starting_unit == "mcg": - return amount - - def to_mg(amount: float, starting_unit: str) -> float: - """Converts from a specified unit to milligrams. - - Args: - amount (float): The amount to convert. - - Returns: - float: The amount converted to milligrams. - """ - if starting_unit == "g": - return amount * 10**3 - - elif starting_unit == "mcg": - return amount / 10**3 - - elif starting_unit == "mg": - return amount - - def to_G(amount: float, starting_unit: str) -> float: - """Converts from a specified unit to grams. - - Args: - amount (float): The amount to convert. - - Returns: - float: The amount converted to Grams. - """ - if starting_unit == "mg": - return amount / 10**3 - - elif starting_unit == "mcg": - return amount / 10**6 - - elif starting_unit == "g": - return amount diff --git a/scripts/create_my_database.py b/scripts/create_my_database.py deleted file mode 100644 index 5f1d890..0000000 --- a/scripts/create_my_database.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Creates the medications which I use at my agency and writes them to the -table.""" - - -from narcotics_tracker import ( - database, -) -from narcotics_tracker.builders import ( - medication_builder, - reporting_period_builder, -) - -FENTANYL_PROPERTIES = [ - "fentanyl", - "Fentanyl", - "VIAL", - 100, - "mcg", - 2, - "ACTIVE", -] - -MIDAZOLAM_PROPERTIES = [ - "midazolam", - "Midazolam", - "VIAL", - 10, - "mg", - 2, - "ACTIVE", -] - -MORPHINE_PROPERTIES = [ - "morphine", - "Morphine", - "VIAL", - 10, - "mg", - 1, - "ACTIVE", -] - -DATABASE_FILES = ["inventory.db", "test_database_2.db"] - - -def build_medication(medication_properties: list): - """Uses the MedicationBuilder to create medication objects.""" - med_builder = medication_builder.MedicationBuilder() - - med_builder.set_medication_code(medication_properties[0]) - med_builder.set_medication_name(medication_properties[1]) - med_builder.set_container(medication_properties[2]) - med_builder.set_dose_and_unit(medication_properties[3], medication_properties[4]) - med_builder.set_fill_amount(medication_properties[5]) - med_builder.set_medication_status(medication_properties[6]) - - return med_builder.build() - - -def main(): - - # Build Medication Objects - fentanyl = build_medication(FENTANYL_PROPERTIES) - fentanyl.created_date = database.return_datetime("2022-08-08") - fentanyl.modified_by = "SRK" - - morphine = build_medication(MORPHINE_PROPERTIES) - morphine.created_date = database.return_datetime("2022-08-08") - morphine.modified_by = "SRK" - - midazolam = build_medication(MIDAZOLAM_PROPERTIES) - midazolam.modified_by = "SRK" - - # Build Reporting Period Objects - - period_builder = reporting_period_builder.ReportingPeriodBuilder() - - period_builder.set_starting_date("2022-01-01 00:00:00") - period_builder.set_ending_date("2022-06-30 23:59:59") - period_builder.set_modified_by("SRK") - period_1 = period_builder.build() - - period_builder = reporting_period_builder.ReportingPeriodBuilder() - - period_builder.set_starting_date("2022-07-01 00:00:00") - period_builder.set_ending_date("2022-12-31 23:59:59") - period_builder.set_modified_by("SRK") - period_2 = period_builder.build() - - period_builder = reporting_period_builder.ReportingPeriodBuilder() - - period_builder.set_starting_date("2023-01-01 00:00:00") - period_builder.set_ending_date("2023-06-30 23:59:59") - period_builder.set_modified_by("SRK") - period_3 = period_builder.build() - - period_builder.set_starting_date("2023-07-01 00:00:00") - period_builder.set_ending_date("2023-12-31 23:59:59") - period_builder.set_modified_by("SRK") - period_4 = period_builder.build() - - for file_name in DATABASE_FILES: - with database.Database(f"{file_name}") as db: - - fentanyl.save(db) - morphine.save(db) - midazolam.save(db) - - period_1.save(db) - period_2.save(db) - period_3.save(db) - period_4.save(db) - - -if __name__ == "__main__": - main() diff --git a/scripts/setup.py b/scripts/setup.py deleted file mode 100644 index feb6608..0000000 --- a/scripts/setup.py +++ /dev/null @@ -1,238 +0,0 @@ -"""This script sets up the Narcotics Tracker. - -This script is intended to be called called the first time the Narcotics -Tracker is being used. It will created the database, tabes, and standard -items. - -Functions: - - create_database: Creates the database file and returns a connection to it. - - create_event_types_table: Creates the event_types table. - - create_inventory_table: Creates the inventory table. - - create_medications_table: Creates the medications table. - - create_reporting_periods_table: Creates the reporting_periods table. - - create_units_table: Creates the units table. - - main: Sets up the Narcotics Tracker database and populates the tables. -""" - -import sqlite3 - -from narcotics_tracker import ( - containers, - database, - events, - inventory, - medications, - reporting_periods, - statuses, - units, -) -from narcotics_tracker.builders import ( - container_builder, - event_builder, - status_builder, - unit_builder, -) -from narcotics_tracker.setup import standard_items - - -def create_database(database_file_name: str = None) -> sqlite3.Connection: - """Creates the database file and returns a connection to it. - - If the file name is not specified the user is prompted to enter it through - the console. - - Args: - - database_file_name (str): Name of the database file. - - Returns: - - db (sqlite3.Connection): Connection to the created database file. - """ - if database_file_name == None: - database_file_name = input("What would you like to name the database file? ") - - return database_file_name - - -def create_containers_table(db_connection: sqlite3.Connection) -> None: - """Creates the containers table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(containers.return_table_creation_query()) - - -def create_events_table(db_connection: sqlite3.Connection) -> None: - """Creates the events table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(events.return_table_creation_query()) - - -def create_inventory_table(db_connection: sqlite3.Connection) -> None: - """Creates the inventory table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(inventory.return_table_creation_query()) - - -def create_medications_table(db_connection: sqlite3.Connection) -> None: - """Creates the medications table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(medications.return_table_creation_query()) - - -def create_reporting_periods_table(db_connection: sqlite3.Connection) -> None: - """Creates the reporting_periods table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(reporting_periods.return_table_creation_query()) - - -def create_statuses_table(db_connection: sqlite3.Connection) -> None: - """Creates the statuses table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(statuses.return_table_creation_query()) - - -def create_units_table(db_connection: sqlite3.Connection) -> None: - """Creates the units table. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - db_connection.create_table(units.return_table_creation_query()) - - -# Populate Tables. -def populate_database_with_standard_containers( - db_connection: sqlite3.Connection, -) -> None: - """Builds and saves standard containers to the database. - - Standard containers are located in the Standard Items module of the Setup - package. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - standard_containers = standard_items.STANDARD_CONTAINERS - - cont_builder = container_builder.ContainerBuilder() - - for container in standard_containers: - cont_builder.assign_all_attributes(container) - built_container = cont_builder.build() - built_container.save(db_connection) - - -def populate_database_with_standard_events(db_connection: sqlite3.Connection) -> None: - """Builds and saves standard events to the database. - - Standard events are located in the Standard Items module of the Setup - package. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - standard_events = standard_items.STANDARD_EVENTS - - e_builder = event_builder.EventBuilder() - - for event in standard_events: - e_builder.assign_all_attributes(event) - built_event = e_builder.build() - built_event.save(db_connection) - - -def populate_database_with_standard_units(db_connection: sqlite3.Connection) -> None: - """Builds and saves standard units to the database. - - Standard units are located in the Standard Items module of the Setup - package. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - standard_units = standard_items.STANDARD_UNITS - - unt_builder = unit_builder.UnitBuilder() - - for unit in standard_units: - unt_builder.assign_all_attributes(unit) - built_unit = unt_builder.build() - built_unit.save(db_connection) - - -def populate_database_with_standard_statuses(db_connection: sqlite3.Connection) -> None: - """Builds and saves standard statuses to the database. - - Standard statuses are located in the Standard Items module of the Setup - package. - - Args: - - db_connection (sqlite3.Connection): The connection to the database. - """ - standard_statuses = standard_items.STANDARD_STATUSES - - stat_builder = status_builder.StatusBuilder() - - for status in standard_statuses: - stat_builder.assign_all_attributes(status) - built_status = stat_builder.build() - built_status.save(db_connection) - - -def main() -> None: - """Sets up the Narcotics Tracker database and populates the tables.""" - database_name = create_database() - with database.Database(database_name) as db: - - create_containers_table(db) - create_events_table(db) - create_inventory_table(db) - create_medications_table(db) - create_reporting_periods_table(db) - create_statuses_table(db) - create_units_table(db) - - populate_database_with_standard_units(db) - populate_database_with_standard_events(db) - populate_database_with_standard_containers(db) - populate_database_with_standard_statuses(db) - - -if __name__ == "__main__": - main() diff --git a/tests/Integration/__init__.py b/tests/Integration/__init__.py new file mode 100644 index 0000000..79a5024 --- /dev/null +++ b/tests/Integration/__init__.py @@ -0,0 +1,5 @@ +"""Contains all the integration testing modules. + +Modules: + Commands Test: Contains integration tests for SQLiteCommands. +""" diff --git a/tests/Integration/adjustment_storage_test.py b/tests/Integration/adjustment_storage_test.py new file mode 100644 index 0000000..c9d28be --- /dev/null +++ b/tests/Integration/adjustment_storage_test.py @@ -0,0 +1,88 @@ +"""Integration tests for handling Adjustments in the SQlite3 database. + +Classes: + Test_AdjustmentStorage: Tests Adjustment Storage in the SQLite3 database. + +Functions: + return_ids: Returns id numbers of DataItems obtained from the database. + +""" + +import sqlite3 + +from narcotics_tracker import commands +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +class Test_AdjustmentStorage: + """Tests Adjustment Storage in the SQLite3 database. + + Behaviors Tested: + - Adjustments can be added to the inventory table. + - Adjustments can be removed from the inventory table. + - Adjustments can be read from the inventory table. + - Adjustments can be updated. + """ + + def test_adjustments_can_be_added(self, reset_database, adjustment) -> None: + adjustment = adjustment + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateInventoryTable(sq_man).execute() + + commands.AddAdjustment(sq_man).execute(adjustment) + + cursor = sq_man.read(table_name="inventory") + adjustment_ids = return_ids(cursor) + assert -1 in adjustment_ids + + def test_adjustments_can_be_removed(self, reset_database, adjustment) -> None: + adjustment = adjustment + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateInventoryTable(sq_man).execute() + commands.AddAdjustment(sq_man).execute(adjustment) + + commands.DeleteAdjustment(sq_man).execute(-1) + + cursor = sq_man.read(table_name="inventory") + adjustment_ids = return_ids(cursor) + assert -1 not in adjustment_ids + + def test_adjustments_can_be_read(self, reset_database, adjustment) -> None: + adjustment = adjustment + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateInventoryTable(sq_man).execute() + commands.AddAdjustment(sq_man).execute(adjustment) + + data = commands.ListAdjustments(sq_man).execute() + + assert data != None + + def test_adjustments_can_be_updated(self, reset_database, adjustment) -> None: + adjustment = adjustment + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateInventoryTable(sq_man).execute() + commands.AddAdjustment(sq_man).execute(adjustment) + + commands.UpdateAdjustment(sq_man).execute({"amount": 9999}, {"id": -1}) + + returned_adjustment = commands.ListAdjustments(sq_man).execute({"id": -1})[0] + + assert returned_adjustment[4] == 9999 diff --git a/tests/Integration/event_storage_test.py b/tests/Integration/event_storage_test.py new file mode 100644 index 0000000..3496b7e --- /dev/null +++ b/tests/Integration/event_storage_test.py @@ -0,0 +1,130 @@ +"""Integration tests for handling Events in the SQlite3 database. + +Classes: + Test_EventStorage: Tests Event Storage in the SQLite3 database. + +Functions: + return_ids: Returns id numbers of DataItems obtained from the database. + +""" + +import sqlite3 + +from narcotics_tracker import commands +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +class Test_EventStorage: + """Tests Event Storage in the SQLite3 database. + + Behaviors Tested: + - Events can be added to the events table. + - Events can be removed from the inventory table. + - Events can be read from the inventory table. + - Events can be updated. + - Event's Modifier can be returned. + """ + + def test_events_can_be_added_to_db(self, event) -> None: + event = event + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + + commands.AddEvent(sq_man).execute(event) + + cursor = sq_man.read(table_name="events") + event_ids = return_ids(cursor) + assert -77 in event_ids + + def test_events_can_be_removed_from_db_using_ID(self, reset_database, event): + event = event + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + commands.AddEvent(sq_man).execute(event) + + commands.DeleteEvent(sq_man).execute(-1) + + cursor = sq_man.read(table_name="events") + event_id = return_ids(cursor) + assert -1 not in event_id + + def test_events_can_be_removed_from_db_using_code(self, reset_database, event): + event = event + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + commands.AddEvent(sq_man).execute(event) + + commands.DeleteEvent(sq_man).execute("TEST") + + cursor = sq_man.read(table_name="events") + event_id = return_ids(cursor) + assert -1 not in event_id + + def test_events_can_be_read_from_db(self, reset_database, event): + event = event + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + commands.AddEvent(sq_man).execute(event) + + data = commands.ListEvents(sq_man).execute() + + assert data != None + + def test_events_can_be_updated_in_db(self, reset_database, event) -> None: + event = event + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + commands.AddEvent(sq_man).execute(event) + + commands.UpdateEvent(sq_man).execute( + data={"event_code": "NEW CODE"}, criteria={"event_code": "TEST"} + ) + + returned_event = commands.ListEvents(sq_man).execute({"id": -77})[0] + + assert "NEW CODE" in returned_event + + def test_event_modifier_can_be_returned(self, reset_database, event) -> None: + event = event + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateEventsTable(sq_man).execute() + commands.AddEvent(sq_man).execute(event) + + results = commands.event_commands.ReturnEventModifier(sq_man).execute("TEST") + assert results == 999 diff --git a/tests/Integration/medication_storage_test.py b/tests/Integration/medication_storage_test.py new file mode 100644 index 0000000..446250f --- /dev/null +++ b/tests/Integration/medication_storage_test.py @@ -0,0 +1,119 @@ +"""Integration tests for handling Medications in the SQlite3 database. + +Classes: + Test_MedicationStorage: Tests Medication Storage in the SQLite3 database. + +Functions: + return_ids: Returns id numbers of DataItems obtained from the database. + +""" + +import sqlite3 + +from narcotics_tracker import commands +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +class Test_MedicationStorage: + """Tests Medication Storage in the SQLite3 database. + + Behaviors Tested: + - Medications can be added to the medications table. + - Medications can be removed from the inventory table. + - Medications can be read from the inventory table. + - Medications can be updated. + - Medication's preferred unit can be returned. + """ + + def test_medications_can_be_added_to_db(self, medication) -> None: + medication = medication + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateMedicationsTable(sq_man).execute() + + commands.AddMedication(sq_man).execute(medication) + + cursor = sq_man.read("medications") + medication_ids = return_ids(cursor) + assert -1 in medication_ids + + def test_medications_can_be_removed_from_db_using_ID( + self, reset_database, medication + ): + medication = medication + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateMedicationsTable(sq_man).execute() + commands.AddMedication(sq_man).execute(medication) + + commands.DeleteMedication(sq_man).execute(-1) + + cursor = sq_man.read("medications") + medication_id = return_ids(cursor) + assert -1 not in medication_id + + def test_medications_can_be_removed_from_db_using_code( + self, reset_database, medication + ): + medication = medication + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateMedicationsTable(sq_man).execute() + commands.AddMedication(sq_man).execute(medication) + + commands.DeleteMedication(sq_man).execute("apap") + + cursor = sq_man.read("medications") + medication_id = return_ids(cursor) + assert -1 not in medication_id + + def test_medications_can_be_read_from_db(self, reset_database, medication): + medication = medication + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateMedicationsTable(sq_man).execute() + commands.AddMedication(sq_man).execute(medication) + + data = commands.ListMedications(sq_man).execute() + + assert data != None + + def test_medications_can_be_updated_in_db(self, reset_database, medication) -> None: + medication = medication + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateMedicationsTable(sq_man).execute() + commands.AddMedication(sq_man).execute(medication) + + commands.UpdateMedication(sq_man).execute( + data={"medication_code": "NEW CODE"}, criteria={"medication_code": "apap"} + ) + + returned_medication = commands.ListMedications(sq_man).execute({"id": -1})[0] + + assert "NEW CODE" in returned_medication + + def test_preferred_unit_can_be_returned(self, reset_database, medication) -> None: + medication = medication + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateMedicationsTable(sq_man).execute() + commands.AddMedication(sq_man).execute(medication) + + results = commands.medication_commands.ReturnPreferredUnit(sq_man).execute( + "apap" + ) + + assert results == "mcg" diff --git a/tests/Integration/reporting_period_storage_test.py b/tests/Integration/reporting_period_storage_test.py new file mode 100644 index 0000000..af846f3 --- /dev/null +++ b/tests/Integration/reporting_period_storage_test.py @@ -0,0 +1,98 @@ +"""Integration tests for handling ReportingPeriods in the SQlite3 database. + +Classes: + Test_ReportingPeriodStorage: Tests ReportingPeriod Storage in the SQLite3 database. + +Functions: + return_ids: Returns id numbers of DataItems obtained from the database. + +""" + +import sqlite3 + +from narcotics_tracker import commands +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +class Test_ReportingPeriodStorage: + """Tests ReportingPeriod Storage in the SQLite3 database. + + Behaviors Tested: + - ReportingPeriods can be added to the reporting_periods table. + - ReportingPeriods can be removed from the inventory table. + - ReportingPeriods can be read from the inventory table. + - ReportingPeriods can be updated. + """ + + def test_ReportingPeriods_can_be_added_to_db(self, reporting_period) -> None: + reporting_period = reporting_period + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateReportingPeriodsTable(sq_man).execute() + + commands.AddReportingPeriod(sq_man).execute(reporting_period) + + cursor = sq_man.read(table_name="reporting_periods") + reporting_period_ids = return_ids(cursor) + assert -1 in reporting_period_ids + + def test_ReportingPeriods_can_be_removed_from_db( + self, reset_database, reporting_period + ): + reporting_period = reporting_period + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateReportingPeriodsTable(sq_man).execute() + commands.AddReportingPeriod(sq_man).execute(reporting_period) + + commands.DeleteReportingPeriod(sq_man).execute(-1) + + cursor = sq_man.read(table_name="reporting_periods") + reporting_period_id = return_ids(cursor) + assert -1 not in reporting_period_id + + def test_ReportingPeriods_can_be_read_from_db( + self, reset_database, reporting_period + ): + reporting_period = reporting_period + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateReportingPeriodsTable(sq_man).execute() + commands.AddReportingPeriod(sq_man).execute(reporting_period) + + data = commands.ListReportingPeriods(sq_man).execute() + + assert data != None + + def test_reporting_periods_can_be_updated_in_db( + self, reset_database, reporting_period + ) -> None: + reporting_period = reporting_period + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateReportingPeriodsTable(sq_man).execute() + commands.AddReportingPeriod(sq_man).execute(reporting_period) + + commands.UpdateReportingPeriod(sq_man).execute( + data={"status": "NEW STATUS"}, criteria={"id": -1} + ) + + returned_reporting_period = commands.ListReportingPeriods(sq_man).execute( + criteria={"id": -1} + )[0] + + assert "NEW STATUS" in returned_reporting_period diff --git a/tests/Integration/status_storage_test.py b/tests/Integration/status_storage_test.py new file mode 100644 index 0000000..7277182 --- /dev/null +++ b/tests/Integration/status_storage_test.py @@ -0,0 +1,102 @@ +"""Integration tests for handling Statuses in the SQlite3 database. + +Classes: + Test_StatusStorage: Tests Status Storage in the SQLite3 database. + +Functions: + return_ids: Returns id numbers of DataItems obtained from the database. + +""" + +import sqlite3 + +from narcotics_tracker import commands +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +class Test_StatusStorage: + """Tests Status Storage in the SQLite3 database. + + Behaviors Tested: + - Statuses can be added to the status table. + - Statuses can be removed from the inventory table. + - Statuses can be read from the inventory table. + - Statuses can be updated. + """ + + def test_Statuses_can_be_added_to_db(self, status) -> None: + status = status + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateStatusesTable(sq_man).execute() + + commands.AddStatus(sq_man).execute(status) + + cursor = sq_man.read(table_name="statuses") + status_ids = return_ids(cursor) + assert -1 in status_ids + + def test_Statuses_can_be_removed_from_db_using_ID(self, reset_database, status): + status = status + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateStatusesTable(sq_man).execute() + commands.AddStatus(sq_man).execute(status) + + commands.DeleteStatus(sq_man).execute(-1) + + cursor = sq_man.read(table_name="statuses") + status_id = return_ids(cursor) + assert -1 not in status_id + + def test_Statuses_can_be_removed_from_db_using_code(self, reset_database, status): + status = status + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateStatusesTable(sq_man).execute() + commands.AddStatus(sq_man).execute(status) + + commands.DeleteStatus(sq_man).execute("BROKEN") + + cursor = sq_man.read(table_name="statuses") + status_id = return_ids(cursor) + assert -1 not in status_id + + def test_statuses_can_be_read_from_db(self, reset_database, status): + status = status + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateStatusesTable(sq_man).execute() + commands.AddStatus(sq_man).execute(status) + + data = commands.ListStatuses(sq_man).execute() + + assert data != None + + def test_statuses_can_be_updated_in_db(self, reset_database, status) -> None: + status = status + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateStatusesTable(sq_man).execute() + commands.AddStatus(sq_man).execute(status) + + commands.UpdateStatus(sq_man).execute( + {"status_code": "NEW CODE"}, {"status_code": "BROKEN"} + ) + + returned_status = commands.ListStatuses(sq_man).execute({"id": -1})[0] + + assert "NEW CODE" in returned_status diff --git a/tests/Integration/table_creation_test.py b/tests/Integration/table_creation_test.py new file mode 100644 index 0000000..b63dd34 --- /dev/null +++ b/tests/Integration/table_creation_test.py @@ -0,0 +1,282 @@ +"""Integration tests for creating tables within the SQLite3 database. + +Classes: + Test_TableCreationCommands: Performs integration testing for commands + that create tables. + +Methods: + return_expected_columns_from_command: Returns a list of column names + created from the given command. + return_column_names_from_db: Returns a list of column names from the + passed SQLiteManager and table. + return_table_names_from_db: Returns a list of table names from the passed + SQLiteManager. +""" + + +from narcotics_tracker.commands import CreateEventsTable, CreateMedicationsTable +from narcotics_tracker.commands.interfaces.command import Command +from narcotics_tracker.commands.table_commands import ( + CreateInventoryTable, + CreateReportingPeriodsTable, + CreateStatusesTable, + CreateUnitsTable, +) +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_expected_columns_from_command( + command: Command, +) -> list[str]: + """Returns a list of column names created from the given command.""" + columns = [] + for item in command._column_info.keys(): + columns.append(item) + return columns + + +def return_column_names_from_db(db: SQLiteManager, table_name: str) -> list[str]: + """Returns a list of column names from the passed SQLiteManager and table.""" + column_names = [] + + cursor = db.read(table_name) + data = cursor.description + + for _tuple in data: + column_names.append(_tuple[0]) + + return column_names + + +def return_table_names_from_db(db: SQLiteManager) -> list[str]: + """Returns a list of table names from the passed SQLiteManager.""" + + cursor = db._execute("""SELECT name FROM sqlite_master WHERE type = 'table'""") + table_names = [] + data = cursor.fetchall() + + for item in data: + table_names.append(item[0]) + + return table_names + + +class Test_EventsTableCreation: + """Tests that 'events' table is created by CreateEventsTable command. + + Behaviors Tested: + - The 'events' table is created in the database. + - All expected columns are created in the table. + """ + + def test_CreateEventsTable_creates_table(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + + CreateEventsTable(sq_manager).execute() + + table_names = return_table_names_from_db(sq_manager) + + assert "events" in table_names + + def test_CreateEventsTable_creates_expected_columns(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + expected_columns = return_expected_columns_from_command(CreateEventsTable) + missing_columns = [] + + CreateEventsTable(sq_manager).execute() + column_names = return_column_names_from_db(sq_manager, "events") + + for column in expected_columns: + if column not in column_names: + missing_columns.append(column) + + if missing_columns != []: + print(missing_columns) + + assert missing_columns == [] + + +class Test_InventoryTableCreation: + """Tests that 'inventory' table is created by CreateInventoryTable command. + + Behaviors Tested: + - The 'inventory' table is created in the database. + - All expected columns are created in the table. + """ + + def test_CreateInventoryTable_creates_table(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + + CreateInventoryTable(sq_manager).execute() + + table_names = return_table_names_from_db(sq_manager) + + assert "inventory" in table_names + + def test_CreateInventoryTable_creates_expected_columns( + self, reset_database + ) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + expected_columns = return_expected_columns_from_command(CreateInventoryTable) + missing_columns = [] + + CreateInventoryTable(sq_manager).execute() + + column_names = return_column_names_from_db(sq_manager, "inventory") + + for column in expected_columns: + if column not in column_names: + missing_columns.append(column) + + if missing_columns != []: + print(missing_columns) + + assert missing_columns == [] + + +class Test_MedicationsTableCreation: + """Tests that 'medications' table is created by CreateMedicationsTable command. + + Behaviors Tested: + - The 'medications' table is created in the database. + - All expected columns are created in the table. + """ + + def test_CreateMedicationsTable_creates_table(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + + CreateMedicationsTable(sq_manager).execute() + + table_names = return_table_names_from_db(sq_manager) + + assert "medications" in table_names + + def test_CreateMedicationsTable_creates_expected_columns( + self, reset_database + ) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + expected_columns = return_expected_columns_from_command(CreateMedicationsTable) + missing_columns = [] + + CreateMedicationsTable(sq_manager).execute() + column_names = return_column_names_from_db(sq_manager, "medications") + + for column in expected_columns: + if column not in column_names: + missing_columns.append(column) + + if missing_columns != []: + print(missing_columns) + + assert missing_columns == [] + + +class Test_ReportingPeriodsTableCreation: + """Tests that the table is created by CreateReportingPeriodsTable command. + + Behaviors Tested: + - The 'reporting_periods' table is created in the database. + - All expected columns are created in the table. + """ + + def test_CreateReportingPeriodsTable_creates_table(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + + CreateReportingPeriodsTable(sq_manager).execute() + + table_names = return_table_names_from_db(sq_manager) + + assert "reporting_periods" in table_names + + def test_CreateReportingPeriodsTable_creates_expected_columns( + self, reset_database + ) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + expected_columns = return_expected_columns_from_command( + CreateReportingPeriodsTable + ) + missing_columns = [] + + CreateReportingPeriodsTable(sq_manager).execute() + + column_names = return_column_names_from_db(sq_manager, "reporting_periods") + + for column in expected_columns: + if column not in column_names: + missing_columns.append(column) + + if missing_columns != []: + print(missing_columns) + + assert missing_columns == [] + + +class Test_StatusesTableCreation: + """Tests that the 'statues' table is created by CreateStatusesTable command. + + Behaviors Tested: + - The 'statuses' table is created in the database. + - All expected columns are created in the table. + """ + + def test_CreateStatusesTable_creates_table(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + + CreateStatusesTable(sq_manager).execute() + + table_names = return_table_names_from_db(sq_manager) + + assert "statuses" in table_names + + def test_CreateStatusesTable_creates_expected_columns(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + expected_columns = return_expected_columns_from_command(CreateStatusesTable) + missing_columns = [] + + CreateStatusesTable(sq_manager).execute() + column_names = return_column_names_from_db(sq_manager, "statuses") + + for column in expected_columns: + if column not in column_names: + missing_columns.append(column) + + if missing_columns != []: + print(missing_columns) + + assert missing_columns == [] + + +class Test_UnitsTableCreation: + """Tests that the 'units' table is created by CreateUnitsTable command. + + Behaviors Tested: + - The 'units' table is created in the database. + - All expected columns are created in the table. + """ + + def test_CreateUnitsTable_creates_table(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + + CreateUnitsTable(sq_manager).execute() + + table_names = return_table_names_from_db(sq_manager) + + assert "units" in table_names + + def test_CreateUnitsTable_creates_expected_columns(self, reset_database) -> None: + sq_manager = SQLiteManager("table_creation_tests.db") + expected_columns = return_expected_columns_from_command(CreateUnitsTable) + missing_columns = [] + + CreateUnitsTable(sq_manager).execute() + + column_names = return_column_names_from_db(sq_manager, "units") + + for column in expected_columns: + if column not in column_names: + missing_columns.append(column) + + if missing_columns != []: + print(missing_columns) + + assert missing_columns == [] diff --git a/tests/Integration/unit_storage_test.py b/tests/Integration/unit_storage_test.py new file mode 100644 index 0000000..bd63ed0 --- /dev/null +++ b/tests/Integration/unit_storage_test.py @@ -0,0 +1,102 @@ +"""Integration tests for handling Units in the SQlite3 database. + +Classes: + Test_UnitStorage: Tests Unit Storage in the SQLite3 database. + +Functions: + return_ids: Returns id numbers of DataItems obtained from the database. + +""" + +import sqlite3 + +from narcotics_tracker import commands +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +def return_ids(cursor: sqlite3.Cursor) -> list[int]: + """Returns id numbers of DataItems obtained from the database. + + Args: + cursor (sqlite3.Cursor): A cursor containing results of a select query. + + Returns: + ids (list[int]): A list of DataItem id numbers. + """ + ids = [] + + raw_data = cursor.fetchall() + for item in raw_data: + ids.append(item[0]) + + return ids + + +class Test_UnitStorage: + """Tests Unit Storage in the SQLite3 database. + + Behaviors Tested: + - Units can be added to the units table. + - Units can be removed from the inventory table. + - Units can be read from the inventory table. + - Units can be updated. + """ + + def test_Units_can_be_added_to_db(self, unit) -> None: + unit = unit + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateUnitsTable(sq_man).execute() + + commands.AddUnit(sq_man).execute(unit) + + cursor = sq_man.read(table_name="units") + unit_ids = return_ids(cursor) + assert -1 in unit_ids + + def test_Units_can_be_removed_from_db_using_ID(self, reset_database, unit): + unit = unit + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateUnitsTable(sq_man).execute() + commands.AddUnit(sq_man).execute(unit) + + commands.DeleteUnit(sq_man).execute(unit_identifier=-1) + + cursor = sq_man.read(table_name="units") + unit_id = return_ids(cursor) + assert -1 not in unit_id + + def test_units_can_be_removed_from_db_using_code(self, reset_database, unit): + unit = unit + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateUnitsTable(sq_man).execute() + commands.AddUnit(sq_man).execute(unit) + + commands.DeleteUnit(sq_man).execute(unit_identifier="dg") + + cursor = sq_man.read(table_name="units") + unit_id = return_ids(cursor) + assert -1 not in unit_id + + def test_units_can_be_read_from_db(self, reset_database, unit): + unit = unit + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateUnitsTable(sq_man).execute() + commands.AddUnit(sq_man).execute(unit) + + data = commands.ListUnits(sq_man).execute() + + assert data != None + + def test_units_can_be_updated_in_db(self, reset_database, unit) -> None: + unit = unit + sq_man = SQLiteManager("data_item_storage_tests.db") + commands.CreateUnitsTable(sq_man).execute() + commands.AddUnit(sq_man).execute(unit) + + commands.UpdateUnit(sq_man).execute( + data={"unit_code": "NEW CODE"}, criteria={"unit_code": "dg"} + ) + + returned_unit = commands.ListUnits(sq_man).execute(criteria={"id": -1})[0] + + assert "NEW CODE" in returned_unit diff --git a/tests/__init__.py b/tests/__init__.py index ac319b0..2c9017a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,6 +4,7 @@ Sub-packages: Unit: Contains all the unit tests. + Integration: Contains all the integration tests. Modules: conftest: Contains all the fixtures and configuration for the tests. diff --git a/tests/conftest.py b/tests/conftest.py index 928a438..205cf6a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,351 +4,154 @@ file contains various fixtures and setting to help with testing. Fixtures: - test_adjustment: Builds and returns a test object from the Event Class. - - test_container: Builds and returns a test object from the Event Class. - - test_db: Connects to and returns the connection to 'test_database.db'. - - test_event: Builds and returns a test object from the Event Class. - - test_medication: Builds and returns a test object from the Medication Class. - - test_period: Builds and returns a test object from the Period Class. - - test_status: Builds and returns a test object from the Status Class. - - test_unit: Builds and returns a test object from the Unit Class. - reset_database: Resets test_database.db for testing functions. """ -from pytest import fixture +import os from typing import TYPE_CHECKING -from narcotics_tracker import ( - containers, - database, - events, - inventory, - reporting_periods, - statuses, - units, -) -from narcotics_tracker.builders import ( - adjustment_builder, - container_builder, - event_builder, - medication_builder, - reporting_period_builder, - status_builder, - unit_builder, -) - -if TYPE_CHECKING: - from narcotics_tracker import medications - - -@fixture -def test_adjustment() -> "inventory.Adjustment": - """Builds and returns a test object from the Adjustment Class. - - Adjustments are used in the inventory module to make changes to the amount - of controlled substance medications. This function uses the builder - pattern to build a adjustment which can be used for testing. - - Review the Inventory Module for more information on adjustments and the - inventory table. - - Review the Database Module for more information on interacting with the - database. - - Review the Events Module for more information on Events and the event_code - which is used as a foreign key within the inventory table. - - Review the Medications Module for more information on Medications and the - medication_code which is used as a foreign key within the inventory table. - - How To Use: - Pass 'test_adjustment' into the test function. - - Assign test_adjustment to a variable and use as needed. - - Returns: - test_adjustment (inventory.Adjustment): An adjustment object for - testing. - """ - - with database.Database("test_database_2.db") as db: - - adj_builder = adjustment_builder.AdjustmentBuilder(db) - adj_builder.assign_adjustment_id(-300) - adj_builder.set_adjustment_date("2022-08-01 10:00:00") - adj_builder.set_event_code("WASTE") - adj_builder.set_medication_code("morphine") - adj_builder.set_adjustment_amount(1) - adj_builder.set_reference_id("TEST ID") - adj_builder.assign_created_date("2022-08-01 10:00:00") - adj_builder.assign_modified_date("2022-08-01 10:00:00") - adj_builder.set_modified_by("Ambrose") +from pytest import fixture - test_adjustment = adj_builder.build() +from narcotics_tracker.builders.adjustment_builder import AdjustmentBuilder +from narcotics_tracker.builders.event_builder import EventBuilder +from narcotics_tracker.builders.medication_builder import MedicationBuilder +from narcotics_tracker.builders.reporting_period_builder import ReportingPeriodBuilder +from narcotics_tracker.builders.status_builder import StatusBuilder +from narcotics_tracker.builders.unit_builder import UnitBuilder - return test_adjustment +if TYPE_CHECKING: + from narcotics_tracker.items.adjustments import Adjustment + from narcotics_tracker.items.events import Event + from narcotics_tracker.items.medications import Medication + from narcotics_tracker.items.reporting_periods import ReportingPeriod + from narcotics_tracker.items.statuses import Status + from narcotics_tracker.items.units import Unit @fixture -def test_container() -> containers.Container: - """Builds and returns a test object from the Container Class. - - Medications come in different containers. These objects are used in the - containers vocabulary control table and the medications table. This - function uses the builder pattern to build a adjustment which can be used - for testing. - - Review the Database Module for more information on interacting with the - database. - - Review the Medications Module for more information on Medications and the - container_type attribute which uses containers. - - How To Use: - Pass 'test_container' into the test function. - - Assign test_container to a variable and use as needed. - - Returns: - test_container (container.Container): A container object for testing. - """ - - cont_builder = container_builder.ContainerBuilder() - - cont_builder.assign_container_id(-7) - cont_builder.set_container_code("supp") - cont_builder.set_container_name("Suppository") - cont_builder.assign_created_date("2022-08-01 00:00:00") - cont_builder.assign_modified_date("2022-08-01 00:00:00") - cont_builder.set_modified_by("Elodin") - - test_container = cont_builder.build() - - return test_container +def adjustment() -> "Adjustment": + """Returns an Adjustment DataItem Object for testing.""" + adj_builder = ( + AdjustmentBuilder() + .set_table("inventory") + .set_id(-1) + .set_created_date(1666117887) + .set_modified_date(1666117887) + .set_modified_by("System") + .set_adjustment_date(1666117887) + .set_event_code("TEST") + .set_medication_code("FakeMed") + .set_adjustment_amount(10) + .set_reference_id("TestReferenceID") + .set_reporting_period_id(0) + ) + + return adj_builder.build() @fixture -def test_event() -> events.Event: - """Builds and returns a test object from the Event Class. - - Events are used in the events vocabulary control table and the inventory - module to define the reason for the inventory change and select whether - the adjustment adds or removes medication from the inventory. This - function uses the builder pattern to build a adjustment which can be used - for testing. - - Review the Inventory Module for more information on adjustments, the - inventory table, and the even_code column which which uses the event_code - as a foreign key from the events table. - - Review the Database Module for more information on interacting with the - database. - - Review the Events Module for more information on Events and the - event_code which is used as a foreign key within the inventory table. - - How To Use: - Pass 'test_event' into the test function. - - Assign test_event to a variable and use as needed. - - Returns: - test_event (event_type.EventType): An EventType object for - testing. - """ - e_builder = event_builder.EventBuilder() - e_builder.assign_event_id(2001) - e_builder.set_event_code("TEST") - e_builder.set_event_name("Test Event") - e_builder.set_description("Used for testing the Event Class.") - e_builder.set_operator(-1) - e_builder.assign_created_date("2022-08-26 00:00:00") - e_builder.assign_modified_date("2022-08-01 00:00:00") - e_builder.set_modified_by("Bast") - - test_event = e_builder.build() - return test_event +def event() -> "Event": + """Returns an Event DataItem Object for testing.""" + event_builder = ( + EventBuilder() + .set_table("events") + .set_id(-77) + .set_created_date(1666117887) + .set_modified_date(1666117887) + .set_modified_by("System") + .set_event_code("TEST") + .set_event_name("Test Event") + .set_description("An event used for testing.") + .set_modifier(999) + ) + + return event_builder.build() @fixture -def test_medication() -> "medications.Medication": - """Builds and returns a test object from the Medication Class. - - Medications are used to specify medication properties in the medications - table. They are also used in the inventory module where they're inventory - amounts are adjusted. This function uses the builder pattern to build a - medication object which can be used for testing. - - Review the Inventory Module for more information on adjustments, the - inventory table, and the medication_code column which which uses the - medication_code as a foreign key from the events table. - - Review the Database Module for more information on interacting with the - database. - - How To Use: - Pass 'test_med' into the test function. - - Assign test_med to a variable and use as needed. - - Returns: - test_med (medication.Medication): A medication object for testing. - """ - med_builder = medication_builder.MedicationBuilder() - med_builder.assign_medication_id(1) - med_builder.set_medication_name("Unobtanium") - med_builder.set_medication_code("Un-69420-9001") - med_builder.set_container("Vial") - med_builder.set_dose_and_unit(69.420, "mg") - med_builder.set_fill_amount(9_001) - med_builder.set_medication_status("Discontinued") - med_builder.assign_created_date("1986-01-02") - med_builder.assign_modified_date("2022-08-09") - med_builder.set_modified_by("Kvothe") - - test_med = med_builder.build() - - return test_med +def medication() -> "Medication": + """Returns a Medication DataItem Object for testing.""" + med_builder = ( + MedicationBuilder() + .set_table("medications") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("SRK") + .set_medication_code("apap") + .set_medication_name("Acetaminophen") + .set_fill_amount(10) + .set_medication_amount(1) + .set_preferred_unit("mcg") + .set_concentration() + .set_status("unknown") + ) + + return med_builder.build() @fixture -def test_period() -> reporting_periods.ReportingPeriod: - """Builds and returns a test object from the Period Class. - - Reporting Periods are used in the reporting_periods vocabulary control - table and the inventory module to organize adjustments into the different - periods in which they must be reported to New York State. This function - uses the builder pattern to build a reporting period object which can be - used for testing. - - Review the Inventory Module for more information on adjustments, the - inventory table, and the reporting_period column which which uses the - reporting_period_id as a foreign key from this table. - - Review the Database Module for more information on interacting with the - database. - - How To Use: - Pass 'test_period' into the test function. - - Assign test_period to a variable and use as needed. - - Returns: - test_period (period.Period): A period object for testing. - """ - - period_builder = reporting_period_builder.ReportingPeriodBuilder() - - period_builder.assign_period_id(9001) - period_builder.set_starting_date("2001-01-01 00:00:00") - period_builder.set_ending_date("2100-06-30 00:00:00") - period_builder.assign_created_date("2022-08-01 00:00:00") - period_builder.assign_modified_date("2022-08-01 00:00:00") - period_builder.set_modified_by("Cinder") - - test_period = period_builder.build() - - return test_period +def reporting_period() -> "ReportingPeriod": + """Returns a ReportingPeriod DataItem Object for testing.""" + reporting_period_builder = ( + ReportingPeriodBuilder() + .set_table("reporting_periods") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("SRK") + .set_start_date(1666061200) + .set_end_date(1666061200) + .set_status("unfinished") + ) + + return reporting_period_builder.build() @fixture -def test_status() -> statuses.Status: - """Builds and returns a test object from the Status Class. - - Statuses are used in the statuses vocabulary control table and the - Medications module to denote the status of a particular medication. This - function uses the builder pattern to build a status object which can be - used for testing. - - Review the Medication Module for more information on Medications, the - medications table, and the status column which which uses the status_code - as a foreign key from this table. - - Review the Database Module for more information on interacting with the - database. - - How To Use: - Pass 'test_status' into the test function. - - Assign test_status to a variable and use as needed. - - Returns: - test_status (statuses.Status): A status object for testing. - """ - - stat_builder = status_builder.StatusBuilder() - - stat_builder.assign_status_id(-19) - stat_builder.set_status_code("ACTIVE") - stat_builder.set_status_name("Active") - stat_builder.set_description("Used for items which are currently in use.") - stat_builder.assign_created_date("2022-08-01 00:00:00") - stat_builder.assign_modified_date("2022-08-01 00:00:00") - stat_builder.set_modified_by("Abenthy") - - test_status = stat_builder.build() - - return test_status +def status() -> "Status": + """Returns a Status DataItem Object for testing.""" + status_builder = ( + StatusBuilder() + .set_table("statuses") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("Systems") + .set_status_code("BROKEN") + .set_status_name("Broken") + .set_description("Used for testing purposes.") + ) + + return status_builder.build() @fixture -def test_unit() -> units.Unit: - """Builds and returns a test object from the Unit Class. - - Units are used in the units vocabulary control table, the Medication - Module to denote the unit of measurement the medication is commonly - measured in, the Unit Converter Module which converts between different - units of measurements as well as the inventory module to set the amount by - which the medication was changed. This function uses the builder pattern - to build a unit object which can be used for testing. - - Review the Medication Module for information on medications, the - medications table and the preferred_unit column which uses the unit_code - as a foreign key from this table. - - Review the Inventory Module for more information on adjustments, the - inventory table, and the amount_in_mcg column which which uses a - medication's preferred_unit to convert the adjustment amount. - - Review the Database Module for more information on interacting with the - database. - - How To Use: - Pass 'test_uni' into the test function. - - Assign test_uni to a variable and use as needed. - - Returns: - test_unit (units.unit): A unit object for testing. - """ - - u_builder = unit_builder.UnitBuilder() - - u_builder.assign_unit_id(821) - u_builder.set_unit_code("tn") - u_builder.set_unit_name("Tina") - u_builder.assign_created_date("2022-08-01 00:00:00") - u_builder.assign_modified_date("2022-08-01 00:00:00") - u_builder.set_modified_by("Denna") - - test_unit = u_builder.build() - - return test_unit +def unit() -> "Unit": + """Returns a Unit DataItem Object for testing.""" + unit_builder = ( + UnitBuilder() + .set_table("units") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("System") + .set_unit_code("dg") + .set_unit_name("Decagrams") + .set_decimals(7) + ) + + return unit_builder.build() @fixture def reset_database(): - """Resets test_database.db for testing functions. + """Resets test_database.db for testing methods.""" + if os.path.exists("data/test_database.db"): + os.remove("data/test_database.db") + + if os.path.exists("data/table_creation_tests.db"): + os.remove("data/table_creation_tests.db") - This function deletes 'data/test_database.db'. - """ - with database.Database("test_database.db") as db: - db.delete_database("test_database.db") + if os.path.exists("data/data_item_storage_tests.db"): + os.remove("data/data_item_storage_tests.db") diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index 45e9901..72911be 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -1,6 +1,6 @@ """Contains all unit testing modules for the Narcotics Tracker. -Modules: +#* Modules: builders_test: Contains all the unit tests for the builders module. database_test: Contains all the unit tests for the database module. diff --git a/tests/unit/builders/__init__.py b/tests/unit/builders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/builders/adjustment_builder_test.py b/tests/unit/builders/adjustment_builder_test.py new file mode 100644 index 0000000..9b17832 --- /dev/null +++ b/tests/unit/builders/adjustment_builder_test.py @@ -0,0 +1,56 @@ +"""Contains the unit tests for the AdjustmentBuilder. + +Classes: + Test_adjustmentBuilder: Unit tests the AdjustmentBuilder. +""" + + +from narcotics_tracker.builders.adjustment_builder import AdjustmentBuilder +from narcotics_tracker.items.adjustments import Adjustment + + +class Test_AdjustmentBuilder: + """Unit tests the AdjustmentBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns an Adjustment Object. + - Returned object has expected attribute values. + """ + + test_adjustment = ( + AdjustmentBuilder() + .set_table("inventory") + .set_id(-77) + .set_created_date(1666117887) + .set_modified_date(1666117887) + .set_modified_by("System") + .set_adjustment_date(1666117887) + .set_event_code("TEST") + .set_medication_code("FakeMed") + .set_adjustment_amount(10) + .set_reference_id("TestReferenceID") + .set_reporting_period_id(0) + .build() + ) + + def test_adjustmentbuilder_can_be_accessed(self) -> None: + assert AdjustmentBuilder.__doc__ != None + + def test_returned_object_is_an_adjustment(self) -> None: + assert isinstance(self.test_adjustment, Adjustment) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_adjustment) == { + "table": "inventory", + "id": -77, + "created_date": 1666117887, + "modified_date": 1666117887, + "modified_by": "System", + "adjustment_date": 1666117887, + "event_code": "TEST", + "medication_code": "FakeMed", + "amount": 10, + "reference_id": "TestReferenceID", + "reporting_period_id": 0, + } diff --git a/tests/unit/builders/dataitem_builder_test.py b/tests/unit/builders/dataitem_builder_test.py new file mode 100644 index 0000000..8764072 --- /dev/null +++ b/tests/unit/builders/dataitem_builder_test.py @@ -0,0 +1,57 @@ +"""Contains the unit tests for the DataItemBuilder. + +Classes: + Test_DataItemBuilder: Unit tests the DataItemBuilder. +""" + + +from narcotics_tracker.builders.adjustment_builder import AdjustmentBuilder +from narcotics_tracker.builders.dataitem_builder import DataItemBuilder +from narcotics_tracker.items.adjustments import Adjustment + + +class Test_AdjustmentBuilder: + """Unit tests the DataItemBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns an Adjustment Object. + - Returned object has expected attribute values. + """ + + test_adjustment = ( + AdjustmentBuilder() + .set_table("no table") + .set_id(-5) + .set_created_date("01-02-1986 14:10:00") + .set_modified_date(1666117887) + .set_modified_by("System") + .set_adjustment_date(3) + .set_event_code(None) + .set_medication_code(None) + .set_adjustment_amount(None) + .set_reference_id(None) + .set_reporting_period_id(None) + .build() + ) + + def test_adjustmentbuilder_can_be_accessed(self) -> None: + assert DataItemBuilder.__doc__ != None + + def test_returned_object_is_an_adjustment(self) -> None: + assert isinstance(self.test_adjustment, Adjustment) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_adjustment) == { + "table": "no table", + "id": -5, + "created_date": 505077000, + "modified_date": 1666117887, + "modified_by": "System", + "adjustment_date": 3, + "event_code": None, + "medication_code": None, + "amount": None, + "reference_id": None, + "reporting_period_id": None, + } diff --git a/tests/unit/builders/event_builder_test.py b/tests/unit/builders/event_builder_test.py new file mode 100644 index 0000000..d2483a1 --- /dev/null +++ b/tests/unit/builders/event_builder_test.py @@ -0,0 +1,52 @@ +"""Contains the unit tests for the EventBuilder. + +Classes: + Test_EventBuilder: Unit tests the EventBuilder. +""" + + +from narcotics_tracker.builders.event_builder import EventBuilder +from narcotics_tracker.items.events import Event + + +class Test_EventBuilder: + """Unit tests the EventBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns an event Object. + - Returned object has expected attribute values. + """ + + test_event = ( + EventBuilder() + .set_table("events") + .set_id(-77) + .set_created_date(1666117887) + .set_modified_date(1666117887) + .set_modified_by("System") + .set_event_code("TEST") + .set_event_name("Test Event") + .set_description("An event used for testing.") + .set_modifier(999) + .build() + ) + + def test_EventBuilder_can_be_accessed(self) -> None: + assert EventBuilder.__doc__ != None + + def test_returned_object_is_an_event(self) -> None: + assert isinstance(self.test_event, Event) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_event) == { + "table": "events", + "id": -77, + "created_date": 1666117887, + "modified_date": 1666117887, + "modified_by": "System", + "event_code": "TEST", + "event_name": "Test Event", + "description": "An event used for testing.", + "modifier": 999, + } diff --git a/tests/unit/builders/medication_builder_test.py b/tests/unit/builders/medication_builder_test.py new file mode 100644 index 0000000..f25862a --- /dev/null +++ b/tests/unit/builders/medication_builder_test.py @@ -0,0 +1,58 @@ +"""Contains the unit tests for the MedicationBuilder. + +Classes: + Test_MedicationBuilder: Unit tests the MedicationBuilder. +""" + + +from narcotics_tracker.builders.medication_builder import MedicationBuilder +from narcotics_tracker.items.medications import Medication + + +class Test_MedicationBuilder: + """Unit tests the MedicationBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns a Medication Object. + - Returned object has expected attribute values. + """ + + test_medication = ( + MedicationBuilder() + .set_table("medications") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("SRK") + .set_medication_code("apap") + .set_medication_name("Acetaminophen") + .set_fill_amount(10) + .set_medication_amount(1) + .set_preferred_unit("mcg") + .set_concentration() + .set_status("unknown") + .build() + ) + + def test_MedicationBuilder_can_be_accessed(self) -> None: + assert MedicationBuilder.__doc__ != None + + def test_returned_object_is_an_Medication(self) -> None: + assert isinstance(self.test_medication, Medication) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_medication) == { + "table": "medications", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "SRK", + "medication_code": "apap", + "medication_name": "Acetaminophen", + "fill_amount": 10, + "medication_amount": 100, + "preferred_unit": "mcg", + "concentration": 0.1, + "status": "unknown", + } diff --git a/tests/unit/builders/reporting_period_builder_test.py b/tests/unit/builders/reporting_period_builder_test.py new file mode 100644 index 0000000..7e8470b --- /dev/null +++ b/tests/unit/builders/reporting_period_builder_test.py @@ -0,0 +1,50 @@ +"""Contains the unit tests for the ReportingPeriodBuilder. + +Classes: + Test_ReportingPeriodBuilder: Unit tests the ReportingPeriodBuilder. +""" + + +from narcotics_tracker.builders.reporting_period_builder import ReportingPeriodBuilder +from narcotics_tracker.items.reporting_periods import ReportingPeriod + + +class Test_ReportingPeriodBuilder: + """Unit tests the ReportingPeriodBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns a ReportingPeriod Object. + - Returned object has expected attribute values. + """ + + test_reporting_period = ( + ReportingPeriodBuilder() + .set_table("reporting_periods") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("SRK") + .set_start_date(1666061200) + .set_end_date(1666061200) + .set_status("unfinished") + .build() + ) + + def test_ReportingPeriodBuilder_can_be_accessed(self) -> None: + assert ReportingPeriodBuilder.__doc__ != None + + def test_returned_object_is_an_reporting_period(self) -> None: + assert isinstance(self.test_reporting_period, ReportingPeriod) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_reporting_period) == { + "table": "reporting_periods", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "SRK", + "start_date": 1666061200, + "end_date": 1666061200, + "status": "unfinished", + } diff --git a/tests/unit/builders/status_builder_test.py b/tests/unit/builders/status_builder_test.py new file mode 100644 index 0000000..9f639fd --- /dev/null +++ b/tests/unit/builders/status_builder_test.py @@ -0,0 +1,50 @@ +"""Contains the unit tests for the StatusBuilder. + +Classes: + Test_StatusBuilder: Unit tests the StatusBuilder. +""" + + +from narcotics_tracker.builders.status_builder import StatusBuilder +from narcotics_tracker.items.statuses import Status + + +class Test_StatusBuilder: + """Unit tests the StatusBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns a Status Object. + - Returned object has expected attribute values. + """ + + test_status = ( + StatusBuilder() + .set_table("statuses") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("Systems") + .set_status_code("BROKEN") + .set_status_name("Broken") + .set_description("Used for testing purposes.") + .build() + ) + + def test_StatusBuilder_can_be_accessed(self) -> None: + assert StatusBuilder.__doc__ != None + + def test_returned_object_is_an_reporting_period(self) -> None: + assert isinstance(self.test_status, Status) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_status) == { + "table": "statuses", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "Systems", + "status_code": "BROKEN", + "status_name": "Broken", + "description": "Used for testing purposes.", + } diff --git a/tests/unit/builders/unit_builder_test.py b/tests/unit/builders/unit_builder_test.py new file mode 100644 index 0000000..53e6f7b --- /dev/null +++ b/tests/unit/builders/unit_builder_test.py @@ -0,0 +1,50 @@ +"""Contains the unit tests for the UnitBuilder. + +Classes: + Test_UnitBuilder: Unit tests the UnitBuilder. +""" + + +from narcotics_tracker.builders.unit_builder import UnitBuilder +from narcotics_tracker.items.units import Unit + + +class Test_UnitBuilder: + """Unit tests the UnitBuilder. + + Behaviors Tested: + - Can be accessed. + - Returns a Unit Object. + - Returned object has expected attribute values. + """ + + test_Unit = ( + UnitBuilder() + .set_table("units") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("System") + .set_unit_code("dg") + .set_unit_name("Decagrams") + .set_decimals(7) + .build() + ) + + def test_UnitBuilder_can_be_accessed(self) -> None: + assert UnitBuilder.__doc__ != None + + def test_returned_object_is_an_reporting_period(self) -> None: + assert isinstance(self.test_Unit, Unit) + + def test_returned_object_had_expected_attributes(self) -> None: + assert vars(self.test_Unit) == { + "table": "units", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "System", + "unit_code": "dg", + "unit_name": "Decagrams", + "decimals": 7, + } diff --git a/tests/unit/builders_test.py b/tests/unit/builders_test.py deleted file mode 100644 index 1d8e3b6..0000000 --- a/tests/unit/builders_test.py +++ /dev/null @@ -1,1005 +0,0 @@ -"""Contains the Test_Builder class used to test the medication_builder module. - -Classes: - - Test_Builder: Contains all unit tests for the medication_builder module. -""" -import pytest - -from narcotics_tracker import database, inventory, medications, units -from narcotics_tracker.builders import ( - adjustment_builder_template, - adjustment_builder, - container_builder, - container_builder_template, - event_builder, - event_builder_template, - medication_builder, - reporting_period_builder, - reporting_period_builder_template, - status_builder_template, - unit_builder, - unit_builder_template, -) - - -class Test_MedicationBuilder: - """Contains all unit tests for the medication_builder module. - - Behaviors Tested: - - MedicationBuilder sets the medication id correctly. - - MedicationBuilder sets the medication name correctly. - - MedicationBuilder sets the medication code correctly. - - MedicationBuilder sets the medication container correctly. - - An exception is raised if the medication container is not valid. - - MedicationBuilder sets the medication fill amount correctly. - - MedicationBuilder sets the medication dose correctly. - - MedicationBuilder sets the medication unit correctly. - - An exception is raised if the medication unit is not valid. - - MedicationBuilder sets the medication concentration correctly. - - MedicationBuilder sets the medication status correctly. - - An exception is raised if the medication status is not valid. - - MedicationBuilder sets the medication created date correctly. - - MedicationBuilder sets the medication modified date correctly. - - MedicationBuilder sets the medication modified by correctly. - - MedicationBuilder calculates the medication concentration correctly. - - Created medication object has type medication.Medication. - """ - - def test_set_medication_id(self): - """Tests that MedicationBuilder sets the medication id correctly. - - Asserts that the medication id returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = 69420 - - med_builder.assign_medication_id(expected) - - assert med_builder.medication_id == expected - - def test_set_name(self): - """Tests that the medication builder sets the name. - - Asserts the medication name returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "Aspirin" - - med_builder.set_medication_name("Aspirin") - - assert med_builder.name == expected - - def test_set_code(self): - """Tests that the medication builder sets the code. - - Asserts that the medication code returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "ASA" - - med_builder.set_medication_code("ASA") - - assert med_builder.medication_code == expected - - def test_set_container(self): - """Tests that the medication builder sets the container type. - - Asserts that the medication container returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "Ampule" - - med_builder.set_container("Ampule") - assert med_builder.container_type == expected - - def test_set_fill_amount(self): - """Tests that the medication builder sets the fill amount. - - Asserts that the medication fill amount returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = 10_000 - - med_builder.set_fill_amount(expected) - - assert med_builder.fill_amount == expected - - def test_set_dose(self): - """Tests that the medication builder sets the dose and unit. - - Asserts that the medication dose returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = 10_000 - - med_builder.set_dose_and_unit(10, "mg") - - assert med_builder.dose == expected - - def test_set_unit(self): - """Tests that the medication builder sets the dose and unit. - - Asserts that the medication dose returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "mg" - - med_builder.set_dose_and_unit(10, "mg") - - assert med_builder.unit == expected - - def test_set_unit_raises_exception_if_invalid(self): - """Tests that the medication builder raises an exception if invalid. - - Passes if an AttributeError exception is raised. - """ - med_builder = medication_builder.MedicationBuilder() - - with pytest.raises(AttributeError): - med_builder.set_unit(units.Unit.INVALID) - - def test_set_concentration(self): - """Tests that the medication builder sets the concentration. - - Asserts that the medication concentration returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = 45 - - med_builder.assign_concentration(expected) - - assert med_builder.concentration == expected - - def test_set_status(self): - """Tests that the medication builder sets the status. - - Asserts that the medication status returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - - med_builder.set_medication_status("Active") - expected = "Active" - - assert med_builder.status == expected - - def test_set_created_date(self): - """Tests that the medication builder sets the created date. - - Asserts that the medication created date returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "2022-09-09" - - med_builder.assign_created_date(expected) - - assert med_builder.created_date == 1662681600 - - def test_set_modified_date(self): - """Tests that the medication builder sets the modified date. - - Asserts that the medication modified date returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "2022-09-09" - - med_builder.assign_modified_date(expected) - - assert med_builder.modified_date == 1662681600 - - def test_set_modified_by(self): - """Tests that the medication builder sets the modified by. - - Asserts that the medication modified by returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = "John Doe" - - med_builder.set_modified_by(expected) - - assert med_builder.modified_by == expected - - def test_calculate_concentration(self): - """Tests that the medication builder sets the concentration. - - Asserts that the medication concentration returns the expected value. - """ - med_builder = medication_builder.MedicationBuilder() - expected = 1 - - med_builder.set_dose_and_unit(10, "mcg") - med_builder.set_fill_amount(10) - - med_builder.calculate_concentration() - - assert med_builder.concentration == expected - - def test_med_builder_creates_medication_object(self): - """Tests that the medication builder creates a medication object. - - Asserts that the medication object returns a Medication object. - """ - med_builder = medication_builder.MedicationBuilder() - expected = medications.Medication - - med_builder.assign_medication_id(None) - med_builder.set_medication_name("Aspirin") - med_builder.set_medication_code("ASA") - med_builder.set_fill_amount(10) - med_builder.set_container("Ampule") - med_builder.set_dose_and_unit(10, "mcg") - med_builder.set_medication_status("Active") - med_builder.assign_created_date(None) - med_builder.assign_modified_date(None) - med_builder.set_modified_by("SRK") - - aspirin = med_builder.build() - - assert isinstance(aspirin, expected) - - -class Test_AdjustmentAbstractBuilder: - """Contains the unit tests for the adjustment_builder_template module. - - Behaviors Tested: - - Module adjustment_builder_template can be accessed. - - Class Adjustment can be accessed. - """ - - def test_adjustment_builder_template_module_can_be_accessed(self) -> None: - """Tests if the adjustment_builder_template exists and is accessible. - - Asserts that adjustment_builder_template.__doc__ does not return - 'None'. - """ - - assert adjustment_builder_template.__doc__ != None - - def test_adjustment_class_can_be_accessed(self) -> None: - """Tests that Adjustment class exists and is accessible. - - Asserts that adjustment_builder_template.Adjustment.__doc__ does not - return 'None'. - """ - assert adjustment_builder_template.Adjustment.__doc__ != None - - -class Test_AdjustmentBuilderModule: - """Contains the unit tests for the adjustment_builder module. - - Behaviors Tested: - - - Module adjustment_builders can be accessed. - """ - - def test_adjustment_builder_module_can_be_accessed(self) -> None: - """Tests that adjustment_builder.py module exists and is accessible. - - Asserts that adjustment_builder.__doc__ does not return 'None'. - """ - assert adjustment_builder.__doc__ != None - - -class Test_AdjustmentBuilder: - """Contains the unit tests for the AdjustmentBuilder class. - - Behaviors Tested: - - - Class AdjustmentBuilder can be accessed. - - AdjustmentBuilder sets the database connection correctly. - - AdjustmentBuilder sets the adjustment id correctly. - - AdjustmentBuilder sets the adjustment date correctly. - - AdjustmentBuilder sets the event code correctly. - - AdjustmentBuilder sets the medication code correctly. - - AdjustmentBuilder sets amount in preferred unit correctly. - - AdjustmentBuilder sets the reference ID correctly. - - AdjustmentBuilder sets the created date correctly. - - AdjustmentBuilder sets the modified date correctly. - - AdjustmentBuilder sets the modified by attribute correctly. - - Created adjustment object has type inventory.Adjustment. - """ - - def test_adjustment_builder_class_can_be_accessed(self) -> None: - """Tests that the AdjustmentBuilder class exists and is accessible. - - Asserts that adjustment_builder.AdjustmentBuilder.__doc__ does not - return 'None'. - """ - assert adjustment_builder.AdjustmentBuilder.__doc__ != None - - def test_adjustment_id_is_set_correctly(self, test_adjustment) -> None: - """Tests that AdjustmentBuilder sets the adjustment's id correctly. - - Loads test_adjustment. - - Asserts that test_adjustment.adjustment_id is set to '-300'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.adjustment_id == -300 - - def test_adjustment_date_is_set_correctly(self, test_adjustment) -> None: - """Tests that AdjustmentBuilder sets the adjustment's date correctly. - - Loads test_adjustment. - - Asserts that test_adjustment.adjustment_date is set to '2022-08-01 06:00:00'. - """ - test_adjustment = test_adjustment - formatted_date = database.format_datetime_from_unixepoch( - test_adjustment.adjustment_date - ) - - assert formatted_date == "2022-08-01 06:00:00" - - def test_event_code_is_set_correctly(self, test_adjustment) -> None: - """Tests that AdjustmentBuilder sets the event_code correctly. - - Loads test_adjustment. - - Asserts that test_adjustment.event_code is set to 'WASTE'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.event_code == "WASTE" - - def test_medication_code_is_set_correctly(self, test_adjustment) -> None: - """Tests that AdjustmentBuilder sets the medication_code correctly. - - Loads test_adjustment. - - Asserts that test_adjustment.medication_code is set to 'morphine'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.medication_code == "morphine" - - def test_amount_in_preferred_unit_is_set_correctly(self, test_adjustment) -> None: - """Tests that amount of medication changed is set correctly. - - Loads test_med. Builds medications table and saves test_med. - - Asserts that test_adjustment.quantity_amount is '1'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.amount_in_preferred_unit == 1 - - def test_exception_raised_if_adjustment_amount_value_is_negative(self) -> None: - """Tests that an exception is raise if adjustment amount is negative. - - Test passes if value error is raise when calling - set_adjustment_amount(-100). - """ - with database.Database("test_database.db") as db: - adj_builder = adjustment_builder.AdjustmentBuilder(db) - with pytest.raises(ValueError): - adj_builder.set_adjustment_amount(-100) - - def test_reference_id_is_set_correctly(self, test_adjustment) -> None: - """Tests that the Adjustment's reference ID is set correctly. - - Loads test_adjustment - - Asserts that test_adjustment.reference_id is 'TEST ID'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.reference_id == "TEST ID" - - def test_created_date_is_set_correctly(self, test_adjustment) -> None: - """Tests that the adjustments created date is set correctly. - - Loads test_adjustment. - - Asserts test_adjustment.created_date is '2022-08-01 10:00:00'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.created_date == database.return_datetime( - "2022-08-01 10:00:00" - ) - - def test_modified_date_is_set_correctly(self, test_adjustment) -> None: - """Tests that the adjustments created date is set correctly. - - Loads test_adjustment. - - Asserts test_adjustment.modified_date is '2022-08-01 10:00:00'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.modified_date == database.return_datetime( - "2022-08-01 10:00:00" - ) - - def test_modified_by_is_set_correctly(self, test_adjustment) -> None: - """Tests that the adjustments created date is set correctly. - - Loads test_adjustment. - - Asserts test_adjustment.modified_by is 'Ambrose'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.modified_by == "Ambrose" - - def test_amount_in_mcg_is_calculated_correctly(self, test_adjustment) -> None: - """Tests that the amount in mcg is calculated correctly. - - Connects to test database. Loads test_adjustment. - - Asserts test_adjustment.amount_in_mcg is 1000. - """ - - test_adjustment = test_adjustment - - assert test_adjustment.amount_in_mcg == -1000 - - def test_reporting_period_is_assigned_correctly(self, test_adjustment) -> None: - """Tests that the correcting period is assigned to the adjustment. - - Loads test_adjustment. - - Asserts test_adjustment.reporting_period_id returns '2'. - """ - test_adjustment = test_adjustment - - assert test_adjustment.reporting_period_id == 2 - - def test_adjustment_objects_is_instance_of_adjustment_class( - self, test_adjustment - ) -> None: - """Tests that the objects are instances of the Adjustment class. - - Loads test_adjustment. - - Asserts that test_adjustment is an instance of inventory.Adjustment - """ - assert isinstance(test_adjustment, inventory.Adjustment) - - -class Test_EventTypeAbstractBuilder: - """Contains the unit tests for the event_type_builder_template module. - - Behaviors Tested: - - Module event_type_builder_template can be accessed. - - Class event_type can be accessed. - """ - - def test_event_builder_template_module_can_be_accessed(self) -> None: - """Tests if the event_type_builder_template exists and is accessible. - - Asserts that event_type_builder_template.__doc__ does not return - 'None'. - """ - - assert event_builder_template.__doc__ != None - - def test_event_class_can_be_accessed(self) -> None: - """Tests that EventType class exists and is accessible. - - Asserts that event_type_builder_template.EventType.__doc__ does not - return 'None'. - """ - assert event_builder_template.Event.__doc__ != None - - -class Test_EventBuilderModule: - """Contains the unit tests for the event_type_builder module. - - Behaviors Tested: - - - Module event_type_builder can be accessed. - """ - - def test_event_builder_can_be_accessed(self) -> None: - """Tests that the module exists and can be accessed. - - Asserts that event_type_builder.__doc__ does not return 'None'. - """ - assert event_builder.__doc__ != None - - -class Test_EventBuilder: - """Contains the unit tests for the EventBuilder class. - - Behaviors Tested: - - - Class EventBuilder can be accessed. - - EventBuilder sets the event id correctly. - - EventBuilder sets the event code correctly. - - EventBuilder sets the event_name correctly. - - EventBuilder sets the description correctly. - - EventBuilder sets the modifier correctly. - - EventBuilder sets the created date correctly. - - EventBuilder sets the modified date correctly. - - EventBuilder sets the modified by attribute correctly. - #! - Created EventType object has type event_type.EventType. - """ - - def test_event_class_can_be_accessed(self) -> None: - """Tests that the EventBuilder class exists and is accessible. - - Asserts that event_type_builder.EventBuilder.__doc__ does not - return 'None'. - """ - assert event_builder.EventBuilder.__doc__ != None - - def test_event_id_is_set_correctly(self, test_event) -> None: - """Tests that the event_id is set correctly. - - Asserts that test_event.event_id is 2001. - """ - test_event = test_event - - assert test_event.event_id == 2001 - - def test_event_code_is_set_correctly(self, test_event) -> None: - """Tests that the event_code is set correctly. - - Asserts that test_event.event_code is TEST. - """ - test_event = test_event - - assert test_event.event_code == "TEST" - - def test_event_name_is_set_correctly(self, test_event) -> None: - """Tests that the event_name is set correctly. - - Asserts that test_event.event_name is Test Event. - """ - test_event = test_event - - assert test_event.event_name == "Test Event" - - def test_description_is_set_correctly(self, test_event) -> None: - """Tests that the description is set correctly. - - Asserts that test_event.description is ;Used for testing the EventType Class.' - """ - test_event = test_event - - assert test_event.description == "Used for testing the Event Class." - - def test_operator_is_set_correctly(self, test_event) -> None: - """Tests that the operator is set correctly. - - Asserts that test_event.operator is -1. - """ - test_event = test_event - - assert test_event.operator == -1 - - def test_created_date_is_set_correctly(self, test_event) -> None: - """Tests that the created_date is set correctly. - - Asserts that test_event.created_date is "2022-08-26 00:00:00". - """ - test_event = test_event - - assert test_event.created_date == database.return_datetime( - "2022-08-26 00:00:00" - ) - - def test_modified_date_is_set_correctly(self, test_event) -> None: - """Tests that the modified_date is set correctly. - - Asserts that test_event.modified_date is "2022-08-01 00:00:00". - """ - test_event = test_event - - assert test_event.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_date_is_set_correctly(self, test_event) -> None: - """Tests that the modified_by is set correctly. - - Asserts that test_event.modified_by is "Bast". - """ - test_event = test_event - - assert test_event.modified_by == "Bast" - - -class Test_ReportingPeriodAbstractBuilder: - """Contains unit tests for the Reporting Period Builder Template module. - - Behaviors Tested: - - Module reporting_period_builder_template can be accessed. - - Class ReportingPeriod can be accessed. - """ - - def test_reporting_period_builder_template_module_can_be_accessed(self) -> None: - """Tests if reporting_period_builder_template exists and is accessible. - - Asserts that reporting_period_builder_template.__doc__ does not return - 'None'. - """ - - assert reporting_period_builder_template.__doc__ != None - - def test_reporting_period_class_can_be_accessed(self) -> None: - """Tests that ReportingPeriod class exists and is accessible. - - Asserts that reporting_period_builder_template.ReportingPeriod.__doc__ does not - return 'None'. - """ - assert reporting_period_builder_template.ReportingPeriod.__doc__ != None - - -class Test_ReportingPeriodBuilderModule: - """Contains the unit tests for the reporting period builder module. - - Behaviors Tested: - - - Module reporting period builder can be accessed. - """ - - def test_reporting_period_builder_can_be_accessed(self) -> None: - """Tests that the module exists and can be accessed. - - Asserts that reporting_period_builder.__doc__ does not return 'None'. - """ - assert reporting_period_builder.__doc__ != None - - -class Test_ReportingPeriodBuilder: - """Contains the unit tests for the ReportingPeriodBuilder class. - - Behaviors Tested: - - - Class ReportingPeriodBuilder can be accessed. - - ReportingPeriodBuilder sets the event id correctly. - - ReportingPeriodBuilder sets the event code correctly. - - ReportingPeriodBuilder sets the event_name correctly. - - ReportingPeriodBuilder sets the description correctly. - - ReportingPeriodBuilder sets the modifier correctly. - - ReportingPeriodBuilder sets the created date correctly. - - ReportingPeriodBuilder sets the modified date correctly. - - ReportingPeriodBuilder sets the modified by attribute correctly. - #! - Created ReportingPeriod object has type event_type.ReportingPeriod. - """ - - def test_reporting_period_class_can_be_accessed(self) -> None: - """Tests that the ReportingPeriodBuilder class exists and is accessible. - - Asserts that reporting_period_builder.ReportingPeriodBuilder.__doc__ does not - return 'None'. - """ - assert reporting_period_builder.ReportingPeriodBuilder.__doc__ != None - - def test_period_id_is_set_correctly(self, test_period) -> None: - """Tests that the period_id is set correctly. - - Asserts that test_period.period_id is 2001. - """ - test_period = test_period - - assert test_period.period_id == 9001 - - def test_starting_date_is_set_correctly(self, test_period) -> None: - """Tests that the starting_date is set correctly. - - Asserts that test_period.starting_date is expected. - """ - test_period = test_period - - assert test_period.starting_date == database.return_datetime( - "2001-01-01 00:00:00" - ) - - def test_ending_date_is_set_correctly(self, test_period) -> None: - """Tests that the ending_date is set correctly. - - Asserts that test_period.ending_date is expected. - """ - test_period = test_period - - assert test_period.ending_date == database.return_datetime( - "2100-06-30 00:00:00" - ) - - def test_created_date_is_set_correctly(self, test_period) -> None: - """Tests that the created_date is set correctly. - - Asserts that test_period.created_date is "2022-08-26 00:00:00". - """ - test_period = test_period - - assert test_period.created_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_date_is_set_correctly(self, test_period) -> None: - """Tests that the modified_date is set correctly. - - Asserts that test_period.modified_date is "2022-08-01 00:00:00". - """ - test_period = test_period - - assert test_period.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_is_set_correctly(self, test_period) -> None: - """Tests that the modified_by is set correctly. - - Asserts that test_period.modified_by is "Cinder". - """ - test_period = test_period - - assert test_period.modified_by == "Cinder" - - -class Test_UnitAbstractBuilder: - """Contains unit tests for the Unit Builder Template module. - - Behaviors Tested: - - Module unit_builder_template can be accessed. - - Class Unit can be accessed. - """ - - def test_unit_builder_template_module_can_be_accessed(self) -> None: - """Tests if unit_builder_template exists and is accessible. - - Asserts that unit_builder_template.__doc__ does not return - 'None'. - """ - - assert unit_builder_template.__doc__ != None - - def test_unit_class_can_be_accessed(self) -> None: - """Tests that Unit class exists and is accessible. - - Asserts that unit_builder_template.Unit.__doc__ does not - return 'None'. - """ - assert unit_builder_template.Unit.__doc__ != None - - -class Test_UnitBuilderModule: - """Contains the unit tests for the unit builder module. - - Behaviors Tested: - - - Module unit builder can be accessed. - """ - - def test_unit_builder_can_be_accessed(self) -> None: - """Tests that the module exists and can be accessed. - - Asserts that unit_builder.__doc__ does not return 'None'. - """ - assert unit_builder.__doc__ != None - - -class Test_UnitBuilder: - """Contains the unit tests for the UnitBuilder class. - - Behaviors Tested: - - - Class UnitBuilder can be accessed. - - UnitBuilder sets the unit_id correctly. - - UnitBuilder sets the unit_code correctly. - - UnitBuilder sets the unit_name correctly. - - UnitBuilder sets the created date correctly. - - UnitBuilder sets the modified date correctly. - - UnitBuilder sets the modified by attribute correctly. - - Created Unit object has type event_type.Unit. - """ - - def test_unit_class_can_be_accessed(self) -> None: - """Tests that the UnitBuilder class exists and is accessible. - - Asserts that unit_builder.UnitBuilder.__doc__ does not - return 'None'. - """ - assert unit_builder.UnitBuilder.__doc__ != None - - def test_unit_id_is_set_correctly(self, test_unit) -> None: - """Tests that the unit_id is set correctly. - - Asserts that test_unit.unit_id is 821. - """ - test_unit = test_unit - - assert test_unit.unit_id == 821 - - def test_unit_code_is_set_correctly(self, test_unit) -> None: - """Tests that the unit_code is set correctly. - - Asserts that test_unit.unit_code is expected. - """ - test_unit = test_unit - - assert test_unit.unit_code == "tn" - - def test_unit_name_is_set_correctly(self, test_unit) -> None: - """Tests that the unit_name is set correctly. - - Asserts that test_unit.unit_name is expected. - """ - test_unit = test_unit - - assert test_unit.unit_name == "Tina" - - def test_created_date_is_set_correctly(self, test_unit) -> None: - """Tests that the created_date is set correctly. - - Asserts that test_unit.created_date is "2022-08-26 00:00:00". - """ - test_unit = test_unit - - assert test_unit.created_date == database.return_datetime("2022-08-01 00:00:00") - - def test_modified_date_is_set_correctly(self, test_unit) -> None: - """Tests that the modified_date is set correctly. - - Asserts that test_unit.modified_date is "2022-08-01 00:00:00". - """ - test_unit = test_unit - - assert test_unit.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_is_set_correctly(self, test_unit) -> None: - """Tests that the modified_by is set correctly. - - Asserts that test_unit.modified_by is "Denna". - """ - test_unit = test_unit - - assert test_unit.modified_by == "Denna" - - -class Test_ContainerAbstractBuilder: - """Contains unit tests for the Container Builder Template module. - - Behaviors Tested: - - Module container_builder_template can be accessed. - - Class Container can be accessed. - """ - - def test_container_builder_template_module_can_be_accessed(self) -> None: - """Tests if container_builder_template exists and is accessible. - - Asserts that container_builder_template.__doc__ does not return - 'None'. - """ - - assert container_builder_template.__doc__ != None - - def test_container_class_can_be_accessed(self) -> None: - """Tests that container class exists and is accessible. - - Asserts that container_builder_template.Container.__doc__ does not - return 'None'. - """ - assert container_builder_template.Container.__doc__ != None - - -class Test_ContainerBuilderModule: - """Contains the container tests for the container builder module. - - Behaviors Tested: - - - Module container builder can be accessed. - """ - - def test_container_builder_can_be_accessed(self) -> None: - """Tests that the module exists and can be accessed. - - Asserts that container_builder.__doc__ does not return 'None'. - """ - assert container_builder.__doc__ != None - - -class Test_ContainerBuilder: - """Contains the unit tests for the ContainerBuilder class. - - Behaviors Tested: - - - Class ContainerBuilder can be accessed. - - ContainerBuilder sets the container_id correctly. - - ContainerBuilder sets the container_code correctly. - - ContainerBuilder sets the container_name correctly. - - ContainerBuilder sets the created date correctly. - - ContainerBuilder sets the modified date correctly. - - ContainerBuilder sets the modified by attribute correctly. - - Created Container object has type containers.Container. - """ - - def test_container_class_can_be_accessed(self) -> None: - """Tests that the ContainerBuilder class exists and is accessible. - - Asserts that container_builder.ContainerBuilder.__doc__ does not - return 'None'. - """ - assert container_builder.ContainerBuilder.__doc__ != None - - def test_container_id_is_set_correctly(self, test_container) -> None: - """Tests that the container_id is set correctly. - - Asserts that test_container.container_id is -7. - """ - test_container = test_container - - assert test_container.container_id == -7 - - def test_container_code_is_set_correctly(self, test_container) -> None: - """Tests that the container_code is set correctly. - - Asserts that test_container.container_code is expected. - """ - test_container = test_container - - assert test_container.container_code == "supp" - - def test_container_name_is_set_correctly(self, test_container) -> None: - """Tests that the container_name is set correctly. - - Asserts that test_container.container_name is expected. - """ - test_container = test_container - - assert test_container.container_name == "Suppository" - - def test_created_date_is_set_correctly(self, test_container) -> None: - """Tests that the created_date is set correctly. - - Asserts that test_container.created_date is "2022-08-26 00:00:00". - """ - test_container = test_container - - assert test_container.created_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_date_is_set_correctly(self, test_container) -> None: - """Tests that the modified_date is set correctly. - - Asserts that test_container.modified_date is "2022-08-01 00:00:00". - """ - test_container = test_container - - assert test_container.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_is_set_correctly(self, test_container) -> None: - """Tests that the modified_by is set correctly. - - Asserts that test_container.modified_by is "Elodin". - """ - test_container = test_container - - assert test_container.modified_by == "Elodin" - - -class Test_StatusAbstractBuilder: - """Contains unit tests for the Status Builder Template module. - - Behaviors Tested: - - Module status_builder_template can be accessed. - - Class Status can be accessed. - """ - - def test_status_builder_template_module_can_be_accessed(self) -> None: - """Tests if status_builder_template exists and is accessible. - - Asserts that status_builder_template.__doc__ does not return - 'None'. - """ - - assert status_builder_template.__doc__ != None - - def test_status_class_can_be_accessed(self) -> None: - """Tests that Status class exists and is accessible. - - Asserts that status_builder_template.Status.__doc__ does not - return 'None'. - """ - assert status_builder_template.Status.__doc__ != None diff --git a/tests/unit/commands/__init__.py b/tests/unit/commands/__init__.py new file mode 100644 index 0000000..3f9fbbb --- /dev/null +++ b/tests/unit/commands/__init__.py @@ -0,0 +1,4 @@ +"""Contains the unit tests for the Commands Package. + +Modules: +""" diff --git a/tests/unit/configuration/__init__.py b/tests/unit/configuration/__init__.py new file mode 100644 index 0000000..a4c2df0 --- /dev/null +++ b/tests/unit/configuration/__init__.py @@ -0,0 +1,5 @@ +"""Contains units tests for the Configuration Package. + +Modules: + service_provider_test: Handles unit testing of the service provider module. + """ diff --git a/tests/unit/containers_test.py b/tests/unit/containers_test.py deleted file mode 100644 index ce7cec4..0000000 --- a/tests/unit/containers_test.py +++ /dev/null @@ -1,375 +0,0 @@ -"""Contains the classes and tests which test the containers module. - -Classes: - - Test_ContainersModule: Contains all unit tests for the containers module. - - Test_ContainerAttributes: Contains unit tests for Unit's attributes. - - Test_ContainerMethods: Contains unit tests for the Unit's methods. -""" - -from narcotics_tracker import database, containers - - -class Test_ContainersModule: - """Contains all unit tests for the Containers module. - - Behaviors Tested: - - Containers module can be accessed. - - Method return_table_creation_query returns correct string. - #! - Method return_containers returns all containers. - #! - Method parse_unit_data returns correct dictionary and values. - """ - - def test_containers_module_can_be_accessed(self) -> None: - """Tests that the containers module exists and can be accessed. - - Asserts that calling containers.__doc__ does not return 'None'. - """ - assert containers.__doc__ != None - - def test_return_table_creation_query_returns_expected_string(self) -> None: - """Tests that the table_creation_query returns the correct string. - - Calls containers.return_table_creation_query(). - - Asserts that expected_query is returned. - """ - expected_query = """CREATE TABLE IF NOT EXISTS containers ( - CONTAINER_ID INTEGER PRIMARY KEY, - CONTAINER_CODE TEXT UNIQUE, - CONTAINER_NAME TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - assert containers.return_table_creation_query() == expected_query - - def test_return_containers_returns_expected_containers( - self, test_container, reset_database - ) -> None: - """Tests that the return_containers method returns the expected containers. - - Loads and saves test_container. Creates and save. 2nd_unit - Calls containers.return_containers(). - - Asserts that containers.return_containers() returns expected data. - """ - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container = test_container - - test_container.save(db) - - containers_list = containers.return_containers(db) - - assert "Container -7: Suppository. Code: 'supp'." in containers_list[0] - - def test_parse_container_data_returns_correct_values( - self, reset_database, test_container - ) -> None: - """Tests if parse_container_data returns dictionary with correct data. - - Resets the database. Creates containers table. Builds and saves - test_container to database. Queries database for unit data and - calls the parser. - - Asserts that dictionary returned assigns the correct data to correct - keys. - """ - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container = test_container - test_container.save(db) - data = test_container.read(db) - dictionary = containers.parse_container_data(data) - - assert ( - dictionary["container_id"] == -7 - and dictionary["container_code"] == "supp" - and dictionary["container_name"] == "Suppository" - ) - - -class Test_ContainerAttributes: - """Contains all unit tests for the Container Class' attributes. - - Behaviors Tested: - - Container class can be accessed. - - Container objects can be created. - - container_id attribute returns correct value. - - container_code attribute returns correct value. - - container_name attribute returns correct value. - - created_date attribute returns correct value. - - modified_date attribute returns correct value. - - modified_by attribute returns correct value. - """ - - def test_container_class_can_be_accessed(self) -> None: - """Tests that the Container Class exists and can be accessed. - - Asserts that calling Container.__doc__ does not return 'None'. - """ - assert containers.Container.__doc__ != None - - def test_can_create_container_objects(self, test_container) -> None: - """Tests that objects can be created from the Container Class. - - Loads test_container. - - Asserts that test_container is an instance of the Container Class. - """ - test_container = test_container - - assert isinstance(test_container, containers.Container) - - def test_container_id_returns_correct_value(self, test_container) -> None: - """Tests that the container_id attribute returns the correct value. - - Loads test_container. - - Asserts test_container.container_id is '-7'. - """ - test_container = test_container - - assert test_container.container_id == -7 - - def test_container_code_returns_correct_value(self, test_container) -> None: - """Tests that the container_code attribute returns the correct value. - - Loads test_container. - - Asserts that test_container.container_code is 'supp'. - """ - test_container = test_container - - assert test_container.container_code == "supp" - - def test_container_name_returns_correct_value(self, test_container) -> None: - """Tests that the container_name attributes returns the correct value. - - Loads test_container. - - Asserts that test_container.container_name is 'Suppository' - """ - test_container = test_container - - assert test_container.container_name == "Suppository" - - def test_created_date_returns_correct_value(self, test_container) -> None: - """Tests that the created_date attributes returns the correct value. - - Loads test_container. - - Asserts that test_container.created_date is '08-26-2022' - """ - test_container = test_container - - assert test_container.created_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_date_returns_correct_value(self, test_container) -> None: - """Tests that the modified_date attributes returns the correct value. - - Loads test_container. - - Asserts that test_container.modified_date is '08-01-2022' - """ - test_container = test_container - - assert test_container.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_returns_correct_value(self, test_container) -> None: - """Tests that the modified_by attributes returns the correct value. - - Loads test_container. - - Asserts that test_container.modified_by is 'Elodin' - """ - test_container = test_container - - assert test_container.modified_by == "Elodin" - - -class Test_containerMethods: - """Contains all unit tests for the Container Class' methods. - - Behaviors Tested: - - __init__ sets attributes correctly. - - __repr__ returns correct string. - - Can save Unit to database. - - Can read Unit data from database. - - Can load Unit from database. - - Can update Unit in database. - - Can delete Unit from database. - - return_attributes returns the correct values. - """ - - def test___init___sets_attributes_correctly(self, test_container) -> None: - """Tests the initializer sets the objects attributes correctly. - - Loads test_container. - - Asserts that container_id, container_code, and container_name attributes are set to - the expected values. - """ - test_container = test_container - - assert ( - test_container.container_id == -7 - and test_container.container_code == "supp" - and test_container.container_name == "Suppository" - ) - - def test___repr___returns_expected_string(self, test_container) -> None: - """Tests that __repr__ returns correct string. - - Loads test_container. Calls str(test_container). - - Asserts that str(test_container) returns: - 'Unit Test. Code: TEST. Used for testing the EventType Class.' - """ - test_container = test_container - - assert str(test_container) == (f"Unit Number -7: Suppository. Code: 'supp'.") - - def test_can_save_container_to_database( - self, test_container, reset_database - ) -> None: - """Tests that containers can be saved to the database. - - Loads test_container. Calls test_container.save. Calls db.return_data() - using the container_id of '821'. - - Asserts that returned data has container_code value of 'tn'. - """ - test_container = test_container - - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container.save(db) - - data = db.return_data( - """SELECT container_code FROM containers WHERE container_id = '-7'""" - ) - - assert data[0][0] == "supp" - - def test_can_read_container_from_database( - self, reset_database, test_container - ) -> None: - """Tests to see if the containers's data can be returned from database. - - Resets the database. Creates containers table. Builds and saves - test_container. Calls test_container.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container = test_container - test_container.save(db) - - data = test_container.read(db)[0] - expected = [-7, "supp", "Suppository"] - - assert ( - data[0] == expected[0] and data[1] == expected[1] and data[2] == expected[2] - ) - - def test_can_load_container_from_database( - self, reset_database, test_container - ) -> None: - """Tests to see if an Unit Object can be loaded from data. - Loads and saves test_container. Creates loaded_unit from data. - - Asserts that test_container and loaded_unit return identical - attributes. - """ - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container = test_container - test_container.save(db) - - loaded_unit = db.load_container("supp") - - assert ( - loaded_unit.return_attributes()[0] == test_container.return_attributes()[0] - and loaded_unit.return_attributes()[1] - == test_container.return_attributes()[1] - and loaded_unit.return_attributes()[2] - == test_container.return_attributes()[2] - ) - - def test_can_update_container_in_database( - self, reset_database, test_container - ) -> None: - """Tests to see if Unit data can be updated. - - Resets database. Creates Unit Table. Builds and saves test_container to - database. Loads test_container as loaded_unit. Changes Name and updates - database. Queries the data. - - Asserts that the returned data has the new name. - """ - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container = test_container - test_container.save(db) - - test_container.container_name = "Not Tina" - - test_container.update(db) - - data = db.return_data( - """SELECT container_name FROM containers WHERE container_code = 'supp'""" - )[0][0] - - assert data == "Not Tina" - - def test_can_delete_container_from_database(self, test_container, reset_database): - """Tests that containers can be deleted from the database. - - Loads test_container. Saves it to database. Then deletes it. Gets data from - containers table. - - Asserts data is empty. - """ - test_container = test_container - - with database.Database("test_database.db") as db: - db.create_table(containers.return_table_creation_query()) - - test_container.save(db) - test_container.delete(db) - - data = db.return_data("""SELECT * FROM containers""") - assert data == [] - - def test_return_attributes(self, test_container): - """Tests that the containers data is correctly returned. - - Loads test_container. Calls test_container.return_attributes(). - - Asserts values returned are expected values. - """ - test_container = test_container - assert test_container.return_attributes() == ( - -7, - "supp", - "Suppository", - database.return_datetime("2022-08-01 00:00:00"), - database.return_datetime("2022-08-01 00:00:00"), - "Elodin", - ) diff --git a/tests/unit/database_test.py b/tests/unit/database_test.py deleted file mode 100644 index 8184269..0000000 --- a/tests/unit/database_test.py +++ /dev/null @@ -1,307 +0,0 @@ -"""Contains the Test_Database class used to test the database module. - -Classes: - - Test_Database: Contains all unit tests for the database module. - -""" - -import os - -from narcotics_tracker import database, events, medications, reporting_periods - - -class Test_Database: - """Contains all unit tests for the database module. - - Attributes: - test_database: The database file used for testing. - - Behaviors Tested: - - Database object can be created. - - Database object can create a database file. - - Database object can connect to a database. - - Database object can delete a database. - - Database object can create tables. - - Database object can return list of tables. - - Database object can return columns in a table. - - Database object can delete tables. - - Database object can update tables. - - Database object can return data from a table. - - Database object can write data to a table. - - Static method: created_date_is_none returns True if no created date. - - Static method: created_date_is_none returns False if created date. - - Database object can create medication object from data. - - """ - - def test_database_object_can_be_created(self): - """Tests that Database object can be created. - - Asserts that the object is an instance of Database. - """ - with database.Database("test_database.db") as db: - - assert isinstance(db, database.Database) - - def test_database_can_create_database_file(self, reset_database): - """Tests that Database can create a database file. - - Asserts that the database file exists in os path. - """ - with database.Database("test_database.db") as db: - assert os.path.exists("data/test_database.db") - - def test_database_can_connect_to_database(self, reset_database): - """Tests that connection can be made to the database. - - Asserts that the database_connection is not None. - """ - with database.Database("test_database.db") as db: - assert db.connection is not None - - def test_database_can_delete_database(self, reset_database): - """Tests that Database can delete a database. - - Creates a database, then deletes it. - Asserts that the database is not in the list of databases - """ - with database.Database("test_database.db") as db: - db.delete_database("test_database.db") - - assert os.path.exists("data/test_database.db") == False - - def test_database_can_create_table(self, reset_database): - """Tests that Database can create a table. - - Creates a table. - - Asserts that the table name is returned when querying table names from - the database. - """ - with database.Database("test_database.db") as db: - db.create_table( - """CREATE TABLE IF NOT EXISTS test_table (test_column TEXT)""" - ) - tables = db.return_table_names() - - assert "test_table" in tables - - def test_database_can_return_list_of_tables(self, reset_database): - """Tests that the writer can return a list of tables. - - Creates two tables, asserts that both tables exist in the list.""" - with database.Database("test_database.db") as db: - db.create_table("""CREATE TABLE IF NOT EXISTS test_table (data TEXT)""") - db.create_table("""CREATE TABLE IF NOT EXISTS test_table_2 (data TEXT)""") - - data = db.return_table_names() - - assert "test_table" in data and "test_table_2" in data - - def test_database_can_return_columns(self, reset_database): - """Tests that Database can return list of columns from table. - - Creates a table with columns 'data' and 'number'. Gets list of columns. - - Asserts that the columns exist in list of column names.""" - with database.Database("test_database.db") as db: - db.create_table( - """CREATE TABLE IF NOT EXISTS test_table (data TEXT, number INTEGER)""" - ) - - columns = db.return_columns("""SElECT * FROM test_table""") - - assert "data" in columns and "number" in columns - - def test_database_can_delete_table(self, reset_database): - """Tests that Database can delete a table. - - Creates table if it doesn't already exist, then deletes it. - - Asserts that the table is not in the final list of tables.""" - with database.Database("test_database.db") as db: - db.create_table( - """CREATE TABLE IF NOT EXISTS test_table (test_column TEXT)""" - ) - - db.delete_table("""DROP TABLE IF EXISTS test_table""") - - final_tables = db.return_table_names() - - assert "test_table" not in final_tables - - def test_database_can_rename_table(self, reset_database): - """Tests that Database can rename a table. - - Creates 'old_table'. Renames 'old_table' to 'new_table'. - - Asserts that 'new_table' is in the final list of tables and - 'old_table' is not. - """ - with database.Database("test_database.db") as db: - db.create_table("""CREATE TABLE IF NOT EXISTS old_table (data TEXT)""") - - db.update_table("""ALTER TABLE old_table RENAME TO new_table""") - tables = db.return_table_names() - - assert "new_table" in tables and "old_table" not in tables - - def test_database_can_add_column_to_table(self, reset_database): - """Test that Database can add a column to a table. - - Creates a table, adds a column. - - Asserts that the column is returned in the list of columns. - """ - with database.Database("test_database.db") as db: - - db.create_table("""CREATE TABLE IF NOT EXISTS test_table (data TEXT)""") - - db.update_table("""ALTER TABLE test_table ADD COLUMN new_column REAL""") - columns = db.return_columns("""SELECT * FROM 'test_table'""") - - assert "new_column" in columns - - def test_database_can_rename_column_in_table(self, reset_database): - """Tests that Database can rename a column in a table. - - Creates a table, adds a column, renames the column. - - Asserts that thew new column name is in the list and the old name is - not. - """ - # Arrange - with database.Database("test_database.db") as db: - - db.create_table("""CREATE TABLE IF NOT EXISTS test_table (data TEXT)""") - - db.update_table("""ALTER TABLE test_table RENAME COLUMN data TO new_data""") - - columns = db.return_columns("""SElECT * FROM test_table""") - assert "new_data" in columns - - def test_database_can_write_data_to_table(self, reset_database): - """Tests that Database can write data to a table. - - Creates a table, writes data to the table. - - Asserts that the data written is returned when querying the table. - """ - with database.Database("test_database.db") as db: - db.create_table("""CREATE TABLE IF NOT EXISTS test_table (data TEXT)""") - - db.write_data( - """INSERT INTO test_table (data) VALUES(?)""", - ["This is the data"], - ) - - data = db.return_data("""SElECT * FROM test_table""") - - assert "This is the data" in data[0] - - def test_created_date_is_none_returns_false_if_created_date_is_set( - self, test_medication - ): - """Tests if the function returns false when created date is set. - - Loads test_medication, a mock object with a created date set. - - Asserts that the function returns 'False'. - """ - test_medication = test_medication - - assert database.Database.created_date_is_none(test_medication) == False - - def test_created_date_set_is_none_returns_true_when_set_to_None( - self, test_medication - ): - """Tests if the function returns true when created date is None. - - Loads test_medication, a mock object with a created date set. Sets the created - date to None. - - Asserts that the function returns 'True'. - """ - test_medication = test_medication - test_medication.created_date = None - - assert database.Database.created_date_is_none(test_medication) == True - - def test_medication_can_be_loaded_from_stored_data( - self, test_medication, reset_database - ): - """Test that a medication object can be loaded from stored data. - - Loads test_medication, saved it to the database, then loads the data from - the saved database and creates a medication object from the data. - - Asserts that the new medication object has the same type as a \ - medication object. - """ - test_medication = test_medication - - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - test_medication.save(db) - - new_med = db.load_medication("Un-69420-9001") - - assert isinstance(new_med, medications.Medication) - - def test_event_can_be_loaded_from_stored_data(self, test_event, reset_database): - """Test that a event_type object can be loaded from stored data. - - Loads test_event, saved it to the database, then loads the data - from the saved database and creates a event_type object from the data. - - Asserts that the new event_type object has the same type as a - event_type object. - """ - test_event = test_event - - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - test_event.save(db) - - new_event = db.load_event("TEST") - - assert isinstance(new_event, events.Event) - - def test_reporting_period_can_be_loaded_from_stored_data( - self, test_period, reset_database - ): - """Test that a reporting_period object can be loaded from stored data. - - Loads test_period, saves it to the database, then loads the data - from the database and creates a ReportingPeriod object from the data. - - Asserts that the new ReportingPeriod object has the same type as a - ReportingPeriod object. - """ - test_period = test_period - - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - test_period.save(db) - - new_period = db.load_reporting_period(9001) - - assert isinstance(new_period, reporting_periods.ReportingPeriod) - - def test_can_return_current_datetime(self) -> None: - """Tests to see if the Database class can get the current date time. - - Asserts that the returned datetime is not 'None'. - """ - - assert database.return_datetime() != None - - def test_can_format_unixepoch_datetime_to_readable_string(self) -> None: - """Tests that unixepoch datetimes can be converted to strings. - - Asserts that the datetime int '505026000' returns 01-02-1986""" - - assert ( - database.format_datetime_from_unixepoch(505026000) == "1986-01-02 00:00:00" - ) diff --git a/tests/unit/events_test.py b/tests/unit/events_test.py deleted file mode 100644 index a354d0c..0000000 --- a/tests/unit/events_test.py +++ /dev/null @@ -1,426 +0,0 @@ -"""Contains the classes and tests which test the events type module. - -Classes: - - Test_EventsModule: Contains all unit tests for the events module. - - Test_EventAttributes: Contains unit tests for Event's attributes. - - Test_EventsMethods: Contains unit tests for the Event's methods. -""" - -from narcotics_tracker import database, events - - -class Test_EventsModule: - """Contains all unit tests for the event types module. - - Behaviors Tested: - - Event Types module can be accessed. - - Method return_table_creation_query returns correct string. - - Method return_events returns all events. - - Method return_operator returns expected result. - - Method parse_event_data returns correct dictionary and values. - """ - - def test_events_module_can_be_accessed(self) -> None: - """Tests that the events module exists and can be accessed. - - Asserts that calling events.__doc__ does not return 'None'. - """ - assert events.__doc__ != None - - def test_return_table_creation_query_returns_expected_string(self) -> None: - """Tests that the table_creation_query returns the correct string. - - Calls events.return_table_creation_query(). - - Asserts that expected_query is returned. - """ - expected_query = """CREATE TABLE IF NOT EXISTS events ( - EVENT_ID INTEGER PRIMARY KEY, - EVENT_CODE TEXT UNIQUE, - EVENT_NAME TEXT, - DESCRIPTION TEXT, - OPERATOR INTEGER, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - assert events.return_table_creation_query() == expected_query - - def test_return_events_returns_expected_events( - self, test_event, reset_database - ) -> None: - """Tests that the return_events method returns the expected events. - - Loads and saves test_event. Creates and save. 2nd_event_type - Calls events.return_events(). - - Asserts that events.return_events() returns expected data. - """ - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event = test_event - - test_event.save(db) - - events_list = events.return_events(db)[0] - - assert ( - "Event Test Event. Code: TEST. Used for testing the Event Class." - in events_list - ) - - def test_parse_event_data_returns_correct_values( - self, reset_database, test_event - ) -> None: - """Tests if parse_event_data returns dictionary with correct data. - - Resets the database. Creates events table. Builds and saves - test_event to database. Queries database for event type data and - calls the parser. - - Asserts that dictionary returned assigns the correct data to correct - keys. - """ - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event = test_event - test_event.save(db) - data = test_event.read(db) - dictionary = events.parse_event_data(data) - - assert ( - dictionary["event_id"] == 2001 - and dictionary["event_code"] == "TEST" - and dictionary["event_name"] == "Test Event" - and dictionary["description"] == "Used for testing the Event Class." - and dictionary["operator"] == -1 - ) - - -class Test_EventAttributes: - """Contains all unit tests for the Event Class' attributes. - - Behaviors Tested: - - Event class can be accessed. - - Event objects can be created. - - event_id attribute returns correct value. - - event_code attribute returns correct value. - - event_name attribute returns correct value. - - description attribute returns correct value. - - operator attribute returns correct value. - - created_date attribute returns correct value. - - modified_date attribute returns correct value. - - modified_by attribute returns correct value. - """ - - def test_event_class_can_be_accessed(self) -> None: - """Tests that the Event Class exists and can be accessed. - - Asserts that calling Event.__doc__ does not return 'None'. - """ - assert events.Event.__doc__ != None - - def test_can_create_event_type_objects(self, test_event) -> None: - """Tests that objects can be created from the Event Class. - - Loads test_event. - - Asserts that test_event is an instance of the Event Class. - """ - test_event = test_event - - assert isinstance(test_event, events.Event) - - def test_event_id_returns_correct_value(self, test_event) -> None: - """Tests that the event_id attribute returns the correct value. - - Loads test_event. Sets the event_id to '9001'. - - Asserts test_event.event_id is '9001'. - """ - test_event = test_event - test_event.event_id = 2001 - - assert test_event.event_id == 2001 - - def test_event_code_returns_correct_value(self, test_event) -> None: - """Tests that the event_code attribute returns the correct value. - - Loads test_event. - - Asserts that test_event.event_code is 'TEST'. - """ - test_event = test_event - - assert test_event.event_code == "TEST" - - def test_event_name_returns_correct_value(self, test_event) -> None: - """Tests that the event_name attributes returns the correct value. - - Loads test_event. - - Asserts that test_event.event_name is 'Test Event' - """ - test_event = test_event - - assert test_event.event_name == "Test Event" - - def test_description_returns_correct_value(self, test_event) -> None: - """Tests that the description attributes returns the correct value. - - Loads test_event. - - Asserts that test_event.description is 'Used for testing the Event Class.' - """ - test_event = test_event - - assert test_event.description == "Used for testing the Event Class." - - def test_operator_returns_correct_value(self, test_event) -> None: - """Tests that the operator attributes returns the correct value. - - Loads test_event. - - Asserts that test_event.operator is -1. - """ - test_event = test_event - - assert test_event.operator == -1 - - def test_created_date_returns_correct_value(self, test_event) -> None: - """Tests that the created_date attributes returns the correct value. - - Loads test_event. - - Asserts that test_event.created_date is '08-26-2022' - """ - test_event = test_event - - assert test_event.created_date == database.return_datetime( - "2022-08-26 00:00:00" - ) - - def test_modified_date_returns_correct_value(self, test_event) -> None: - """Tests that the modified_date attributes returns the correct value. - - Loads test_event. - - Asserts that test_event.modified_date is '08-01-2022' - """ - test_event = test_event - - assert test_event.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_returns_correct_value(self, test_event) -> None: - """Tests that the modified_by attributes returns the correct value. - - Loads test_event. - - Asserts that test_event.modified_by is 'Bast' - """ - test_event = test_event - - assert test_event.modified_by == "Bast" - - -class Test_EventMethods: - """Contains all unit tests for the Event Class' methods. - - Behaviors Tested: - - __init__ sets attributes correctly. - - __repr__ returns correct string. - - Can save Event to database. - - Can read Event data from database. - - Can load Event from database. - - Can update Event in database. - - Can delete Event from database. - - return_attributes returns the correct values. - """ - - def test___init___sets_attributes_correctly(self, test_event) -> None: - """Tests the initializer sets the objects attributes correctly. - - Loads test_event. - - Asserts that event_id, event_code, event_name and description - attributes are set to the expected values. - """ - test_event = test_event - - assert ( - test_event.event_id == 2001 - and test_event.event_code == "TEST" - and test_event.event_name == "Test Event" - and test_event.description == "Used for testing the Event Class." - ) - - def test___repr___returns_expected_string(self, test_event) -> None: - """Tests that __repr__ returns correct string. - - Loads test_event. Calls str(test_event). - - Asserts that str(test_event) returns: - 'Event Type Test. Code: TEST. Used for testing the Event Class.' - """ - test_event = test_event - - assert str(test_event) == ( - f"Event Test Event. Code: TEST. Used for testing the " f"Event Class." - ) - - def test_can_save_event_type_to_database(self, test_event, reset_database) -> None: - """Tests that event types can be saved to the database. - - Loads test_event. Calls test_event.save. Calls db.return_data() - using the event_id of '2001'. - - Asserts that returned data has event_code value of 'TEST'. - """ - test_event = test_event - - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event.save(db) - - data = db.return_data( - """SELECT event_code FROM events WHERE event_id = '2001'""" - ) - - assert data[0][0] == "TEST" - - def test_can_read_event_type_from_database( - self, reset_database, test_event - ) -> None: - """Tests to see if the event's data can be returned from database. - - Resets the database. Creates events table. Builds and saves - test_event. Calls test_event.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event = test_event - test_event.save(db) - - data = test_event.read(db)[0] - expected = [ - 2001, - "TEST", - "Test Event", - "Used for testing the Event Class.", - -1, - ] - - assert ( - data[0] == expected[0] - and data[1] == expected[1] - and data[2] == expected[2] - and data[3] == expected[3] - and data[4] == expected[4] - ) - - def test_can_load_event_from_database(self, reset_database, test_event) -> None: - """Tests to see if an Event Type Object can be loaded from data. - Loads and saves test_event. Creates loaded_event from data. - - Asserts that test_event and loaded_event_type return identical - attributes. - """ - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event = test_event - test_event.save(db) - - loaded_event_type = db.load_event("TEST") - - assert ( - loaded_event_type.return_attributes()[0] - == test_event.return_attributes()[0] - and loaded_event_type.return_attributes()[1] - == test_event.return_attributes()[1] - and loaded_event_type.return_attributes()[2] - == test_event.return_attributes()[2] - and loaded_event_type.return_attributes()[3] - == test_event.return_attributes()[3] - and loaded_event_type.return_attributes()[4] - == test_event.return_attributes()[4] - ) - - def test_can_update_event_type_in_database( - self, reset_database, test_event - ) -> None: - """Tests to see if Event data can be updated. - - Resets database. Creates Event Type Table. Builds and saves - test_event to database.Loads test_event as - loaded_event_type. Changes Operator and updates database. Queries the - data. - - Asserts that the returned data has the new operator. - """ - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event = test_event - test_event.save(db) - - loaded_event_type = db.load_event("TEST") - loaded_event_type.operator = +10 - - loaded_event_type.update(db) - - data = db.return_data( - """SELECT operator FROM events WHERE event_code = 'TEST'""" - )[0][0] - - assert data == +10 - - def test_can_delete_event_type_from_database(self, test_event, reset_database): - """Tests that event types can be deleted from the database. - - Loads test_event. Saves it to database. Then deletes it. Gets data from - events table. - - Asserts data is empty. - """ - test_event = test_event - - with database.Database("test_database.db") as db: - db.create_table(events.return_table_creation_query()) - - test_event.save(db) - test_event.delete(db) - - data = db.return_data("""SELECT * FROM events""") - assert data == [] - - def test_return_attributes(self, test_event): - """Tests that the event types data is correctly returned. - - Loads test_event. Calls test_event.return_attributes(). - - Asserts values returned are expected values. - """ - test_event = test_event - assert test_event.return_attributes() == ( - 2001, - "TEST", - "Test Event", - "Used for testing the Event Class.", - -1, - database.return_datetime("2022-08-26 00:00:00"), - database.return_datetime("2022-08-01 00:00:00"), - "Bast", - ) diff --git a/tests/unit/inventory_test.py b/tests/unit/inventory_test.py deleted file mode 100644 index e882654..0000000 --- a/tests/unit/inventory_test.py +++ /dev/null @@ -1,498 +0,0 @@ -"""Contains the classes that unit test the inventory module. - -Classes: - - Test_InventoryModule: Contains the unit tests for the inventory module. - - Test_AdjustmentAttributes: Contains unit tests for the class' attributes. -""" - -from narcotics_tracker import database, inventory - - -class Test_InventoryModule: - """Contains the unit tests for the inventory module. - - Behaviors Tested: - - Inventory module can be accessed. - - Method return_table_creation_query returns expected string. - - Method parse_adjustment_data returns correct dictionary and values. - """ - - def test_can_access_inventory_module(self) -> None: - """Tests the the inventory module exists and can be accessed. - - Asserts that inventory.__doc__ does not return nothing. - """ - assert inventory.__doc__ != None - - def test_return_table_creation_query_returns_expected_string(self) -> None: - """Tests that the return_table_creation_query returns as expected. - - Asserts that method returns the expected string. - """ - assert ( - inventory.return_table_creation_query() - == """CREATE TABLE IF NOT EXISTS inventory ( - ADJUSTMENT_ID INTEGER PRIMARY KEY, - ADJUSTMENT_DATE INTEGER, - EVENT_CODE TEXT, - MEDICATION_CODE TEXT, - AMOUNT_IN_MCG REAL, - REPORTING_PERIOD_ID INTEGER, - REFERENCE_ID TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT, - FOREIGN KEY (EVENT_CODE) REFERENCES events (EVENT_CODE) ON UPDATE CASCADE, - FOREIGN KEY (MEDICATION_CODE) REFERENCES medications (MEDICATION_CODE) ON UPDATE CASCADE, - FOREIGN KEY (REPORTING_PERIOD_ID) REFERENCES reporting_periods (PERIOD_ID) ON UPDATE CASCADE - )""" - ) - - def test_parse_adjustment_data_returns_correct_values( - self, test_adjustment - ) -> None: - """Tests if parse_adjustment_data returns dictionary with valid data. - - Connects to test_database_2.db. Creates adjustments table. Builds and - saves test_adjustment to database. Queries database for adjustment - data and calls the parser. - - Asserts that dictionary returned assigns the correct data to correct - keys. - """ - with database.Database("test_database_2.db") as db: - db.create_table(inventory.return_table_creation_query()) - - test_adjustment = test_adjustment - test_adjustment.save(db) - test_adjustment.update(db) - data = test_adjustment.read(db) - dictionary = inventory.parse_adjustment_data(data) - - assert ( - dictionary["adjustment_id"] == -300 - and dictionary["adjustment_date"] - == database.return_datetime("2022-08-01 10:00:00") - and dictionary["event_code"] == "WASTE" - and dictionary["medication_code"] == "morphine" - and dictionary["amount_in_mcg"] == -1000 - and dictionary["reference_id"] == "TEST ID" - ) - - -class Test_AdjustmentAttributes: - """Contains unit tests for the Adjustment Class' attributes. - - Behaviors Tested: - - Adjustments class can be accessed. - - Adjustments return expected database_connection. - - Adjustments return expected adjustment_ID. - - Adjustments return expected adjustment_date. - - Adjustments return expected event_code. - - Adjustments return expected medication_code. - - Adjustments return expected amount_in_preferred_unit - - Adjustments return expected amount_in_mcg. - - Adjustments return expected reporting_period_id. - - Adjustments return expected reference_id. - - Adjustments return expected created_date. - - Adjustments return expected modified_date. - - Adjustments return expected modified_by. - - Adjustments can be edited. - """ - - def test_can_access_adjustment_class(self) -> None: - """Tests that the Adjustment class exists and can be accessed. - - Asserts that inventory.Adjustment.__doc__ does not return nothing. - """ - assert inventory.Adjustment.__doc__ != None - - def test_adjustments_return_expected_adjustment_id(self, test_adjustment) -> None: - """Tests that the correct adjustment_id is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.adjustment_id is -300.""" - test_adjustment = test_adjustment - - assert test_adjustment.adjustment_id == -300 - - def test_adjustments_return_expected_adjustment_date(self, test_adjustment) -> None: - """Tests that the correct adjustment_date is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.adjustment_date is 1659348000.""" - test_adjustment = test_adjustment - - assert test_adjustment.adjustment_date == database.return_datetime( - "2022-08-01 10:00:00" - ) - - def test_adjustments_return_expected_event_code(self, test_adjustment) -> None: - """Tests that the correct event_code is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.event_code is WASTE.""" - test_adjustment = test_adjustment - - assert test_adjustment.event_code == "WASTE" - - def test_adjustments_return_expected_medication_code(self, test_adjustment) -> None: - """Tests that the correct medication_code is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.medication_code is morphine.""" - test_adjustment = test_adjustment - - assert test_adjustment.medication_code == "morphine" - - def test_adjustments_return_expected_amount_in_preferred_unit( - self, test_adjustment - ) -> None: - """Tests that the correct amount_in_preferred_unit is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.amount_in_preferred_unit is 1.""" - test_adjustment = test_adjustment - - assert test_adjustment.amount_in_preferred_unit == 1 - - def test_adjustments_return_expected_amount_in_mcg(self, test_adjustment) -> None: - """Tests that the correct amount_in_mcg is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.amount_in_mcg is 1000.""" - test_adjustment = test_adjustment - - assert test_adjustment.amount_in_mcg == -1000 - - def test_adjustments_return_expected_reporting_period_id( - self, test_adjustment - ) -> None: - """Tests that the correct reporting_period_id is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.reporting_period_id is 2.""" - test_adjustment = test_adjustment - - assert test_adjustment.reporting_period_id == 2 - - def test_adjustments_return_expected_reference_id(self, test_adjustment) -> None: - """Tests that the correct reference_id is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.reference_id is TEST ID.""" - test_adjustment = test_adjustment - - assert test_adjustment.reference_id == "TEST ID" - - def test_adjustments_return_expected_created_date(self, test_adjustment) -> None: - """Tests that the correct created_date is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.created_date is 1659348000.""" - test_adjustment = test_adjustment - - assert test_adjustment.created_date == 1659348000 - - def test_adjustments_return_expected_modified_date(self, test_adjustment) -> None: - """Tests that the correct modified_date is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.modified_date is Tomorrow.""" - test_adjustment = test_adjustment - - assert test_adjustment.modified_date == 1659348000 - - def test_adjustments_return_expected_modified_by(self, test_adjustment) -> None: - """Tests that the correct modified_by is returned. - - Loads test_adjustment. - - Asserts that test_adjustment.modified_by is Ambrose.""" - test_adjustment = test_adjustment - - assert test_adjustment.modified_by == "Ambrose" - - def test_adjustment_attributes_can_be_edited(self, test_adjustment) -> None: - """Tests that an Adjustment's attributes can be edited. - - Loads test_adjustment. Changes test_adjustment.modified_by. - - Asserts that test_adjustment.modified_by equals new value. - """ - test_adjustment = test_adjustment - - test_adjustment.modified_by = "Me, Duh!" - - assert test_adjustment.modified_by == "Me, Duh!" - - -class Test_AdjustmentMethods: - """Contains all unit tests for the methods of the Adjustment class. - - Behaviors Tested: - - __init__ sets_attributes_correctly. - - __repr__ returns correct string. - - Method return_attributes returns the correct information. - - Adjustments can be saved to inventory table. - - Adjustments can be deleted from database. - - Can save Adjustments to database. - - Can read Adjustment data from the database. - - Can load Adjustments from the database. - - Can update Adjustment in database. - - Can delete Adjustments from database. - - return_attributes returns the correct values. - """ - - def test___init___sets_attributes_correctly(self, test_adjustment) -> None: - """Tests the initializer sets the objects attributes correctly. - - Loads test_adjustment. - - Asserts that adjustment_id, adjustment_date event_code, - medication_code, amount_in_mcg and reference_id attributes are set to - the expected values. - """ - test_adjustment = test_adjustment - - assert ( - test_adjustment.adjustment_id == -300 - and test_adjustment.adjustment_date - == database.return_datetime("2022-08-01 10:00:00") - and test_adjustment.event_code == "WASTE" - and test_adjustment.medication_code == "morphine" - and test_adjustment.amount_in_mcg == -1000 - and test_adjustment.reference_id == "TEST ID" - ) - - def test__repr___returns_correct_string(self, test_adjustment) -> None: - """Tests that __repr__ returns correct string. - - Loads test_adjustment. Calls str(test_adjustment). - - - Asserts that str(test_adjustment) returns: - "Adjustment Number -300: 1 mg of Morphine wasted on 2022-08-01 - 06:00:00." - """ - test_adjustment = test_adjustment - - assert str(test_adjustment) == ( - "Adjustment Number -300: 1 mg of Morphine wasted on 2022-08-01 06:00:00." - ) - - def test_adjustment_can_be_saved_to_inventory_table(self, test_adjustment) -> None: - """Tests that adjustments can be saved to the database. - - Loads test_adjustment. Saves it to the database. - - Asserts that the adjustment is present when querying the table. - """ - with database.Database("test_database_2.db") as db: - - test_adjustment = test_adjustment - test_adjustment.save(db) - - data = db.return_data("""SELECT adjustment_id FROM inventory""")[1] - - assert -300 in data - - def test_can_read_adjustment_from_database(self, test_adjustment) -> None: - """Tests to see if the adjustment's data can be returned from database. - - Builds and saves test_adjustment. Calls test_adjustment.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database_2.db") as db: - db.create_table(inventory.return_table_creation_query()) - - test_adjustment = test_adjustment - test_adjustment.save(db) - test_adjustment.update(db) - - data = test_adjustment.read(db)[0] - expected = [ - -300, - database.return_datetime("2022-08-01 10:00:00"), - "WASTE", - "morphine", - -1000, - 2, - "TEST ID", - ] - - assert ( - data[0] == expected[0] - and data[1] == expected[1] - and data[2] == expected[2] - and data[3] == expected[3] - and data[4] == expected[4] - and data[5] == expected[5] - and data[6] == expected[6] - ) - - def test_can_load_adjustment_from_database(self, test_adjustment) -> None: - """Tests to see if an Adjustment Object can be loaded from data. - - Loads and saves test_adjustment. Creates loaded_adjustment from data. - - Asserts that test_adjustment and loaded_adjustment return identical - attributes. - """ - with database.Database("test_database_2.db") as db: - db.create_table(inventory.return_table_creation_query()) - - test_adjustment = test_adjustment - test_adjustment.save(db) - test_adjustment.update(db) - - loaded_adjustment = db.load_adjustment(-300, db) - - assert ( - loaded_adjustment.return_attributes()[0] - == test_adjustment.return_attributes()[0] - and loaded_adjustment.return_attributes()[1] - == test_adjustment.return_attributes()[1] - and loaded_adjustment.return_attributes()[2] - == test_adjustment.return_attributes()[2] - and loaded_adjustment.return_attributes()[3] - == test_adjustment.return_attributes()[3] - and loaded_adjustment.return_attributes()[4] - == test_adjustment.return_attributes()[4] - and loaded_adjustment.return_attributes()[5] - == test_adjustment.return_attributes()[5] - ) - - def test_can_update_adjustment_in_database(self, test_adjustment) -> None: - """Tests to see if Adjustment data can be updated. - - Builds and saves test_adjustment to database. Loads test_adjustment as - loaded_adjustment. Changes reference_is and updates database. Queries the - data. - - Asserts that the returned data has the new reference_id. - """ - with database.Database("test_database_2.db") as db: - db.create_table(inventory.return_table_creation_query()) - - test_adjustment = test_adjustment - test_adjustment.save(db) - - loaded_adjustment = db.load_adjustment(-300, db) - loaded_adjustment.reference_id = "New ID" - - loaded_adjustment.update(db) - - data = db.return_data( - """SELECT reference_id FROM inventory WHERE adjustment_id = -300""" - )[0][0] - - assert data == "New ID" - - def test_updating_changes_amount_in_mcg_appropriately( - self, test_adjustment - ) -> None: - """Tests that the amount_in_mcg is adjusted if operator is changed. - - Loads and saves test_adjustment. Changes test_adjustment's event_code - to IMPORT, and updates the database. - - Asserts that the amount_in_mcg is correctly changed from -1000 to 1000. - """ - test_adjustment = test_adjustment - - with database.Database("test_database_2.db") as db: - test_adjustment.save(db) - - test_adjustment.event_code = "IMPORT" - - with database.Database("test_database_2.db") as db: - test_adjustment.update(db) - - data = db.return_data( - """SELECT amount_in_mcg FROM inventory WHERE adjustment_id = -300""" - )[0][0] - - assert data == 1000 - - def test_amount_in_mcg_does_not_change_if_operator_is_the_same( - self, test_adjustment - ) -> None: - """Tests that amount_in_mcg isn't changes if the operator is unchanged. - - Loads test_adjustment and changes it's id to -999. Saves to database. - Changes test_adjustment.event_code to "LOSS" and updates. - - Asserts that amount_in_mcg is still -1000. - """ - test_adjustment = test_adjustment - test_adjustment.adjustment_id = -999 - - with database.Database("test_database_2.db") as db: - test_adjustment.save(db) - - test_adjustment.event_code = "LOSS" - - with database.Database("test_database_2.db") as db: - test_adjustment.update(db) - - data = db.return_data( - """SELECT amount_in_mcg FROM inventory WHERE adjustment_id= -999""" - )[0][0] - - assert data == -1000 - - def test_can_delete_adjustment_from_database(self, test_adjustment): - """Tests that the adjustment can be deleted from the database. - - Loads test_adjustment. Saves it to database. Then deletes it. - Gets data from adjustment table. Re-saves test_adjustment to database. - - Asserts data does not contain test_adjustment.adjustment_id. - """ - test_adjustment = test_adjustment - - with database.Database("test_database_2.db") as db: - db.create_table(inventory.return_table_creation_query()) - - test_adjustment.save(db) - test_adjustment.delete(db) - - data = db.return_data("""SELECT adjustment_id FROM inventory""") - test_adjustment.save(db) - - assert test_adjustment.adjustment_id not in data - - def test_return_attributes(self, test_adjustment): - """Tests that the adjustment data is correctly returned. - - Loads test_adjustment. Calls test_adjustment.return_attributes(). - - Asserts values returned are expected values. - """ - test_adjustment = test_adjustment - assert test_adjustment.return_attributes() == ( - -300, - 1659348000, - "WASTE", - "morphine", - -1000, - 2, - "TEST ID", - 1659348000, - 1659348000, - "Ambrose", - ) diff --git a/tests/unit/items/__init__.py b/tests/unit/items/__init__.py new file mode 100644 index 0000000..b6af632 --- /dev/null +++ b/tests/unit/items/__init__.py @@ -0,0 +1,5 @@ +"""Contains modules which unit test the data items for the Narcotics Tracker. + +Modules: + +""" diff --git a/tests/unit/items/adjustments_test.py b/tests/unit/items/adjustments_test.py new file mode 100644 index 0000000..a894206 --- /dev/null +++ b/tests/unit/items/adjustments_test.py @@ -0,0 +1,96 @@ +"""Unit tests the Adjustments Module. + +Classes: + + Test_Adjustment: Unit tests the Adjustment Class. +""" + +from narcotics_tracker.items.adjustments import Adjustment + + +class Test_Adjustment: + """Unit tests the Adjustment Class. + + Behaviors Tested: + - Adjustments class can be accessed. + - Adjustments return expected id. + - Adjustments return expected adjustment_date. + - Adjustments return expected event_code. + - Adjustments return expected medication_code. + - Adjustments return expected adjustment_amount. + - Adjustments return expected reporting_period_id. + - Adjustments return expected reference_id. + - Adjustments return expected created_date. + - Adjustments return expected modified_date. + - Adjustments return expected modified_by. + - Adjustments return expected string. + - Adjustments return expected dictionary. + """ + + test_adjustment = Adjustment( + table="inventory", + id=-1, + adjustment_date=524990800, + event_code="BIRTH", + medication_code="TINA", + amount=1, + reference_id="Tina's Mom", + reporting_period_id=-36, + created_date=1666061200, + modified_date=1666061200, + modified_by="SRK", + ) + + def test_adjustment_class_can_be_accessed(self) -> None: + assert Adjustment.__doc__ != None + + def test_adjustments_return_expected_id(self) -> None: + assert self.test_adjustment.id == -1 + + def test_adjustments_return_expected_adjustment_date(self) -> None: + assert self.test_adjustment.adjustment_date == 524990800 + + def test_adjustments_return_expected_event_code(self) -> None: + assert self.test_adjustment.event_code == "BIRTH" + + def test_adjustments_return_expected_medication_code(self) -> None: + assert self.test_adjustment.medication_code == "TINA" + + def test_adjustments_return_expected_adjustment_amount(self) -> None: + assert self.test_adjustment.amount == 1 + + def test_adjustments_return_expected_reporting_period_id(self) -> None: + assert self.test_adjustment.reporting_period_id == -36 + + def test_adjustments_return_expected_reference_id(self) -> None: + assert self.test_adjustment.reference_id == "Tina's Mom" + + def test_adjustments_return_expected_created_date(self) -> None: + assert self.test_adjustment.created_date == 1666061200 + + def test_adjustments_return_expected_modified_date(self) -> None: + assert self.test_adjustment.modified_date == 1666061200 + + def test_adjustments_return_expected_modified_by(self) -> None: + assert self.test_adjustment.modified_by == "SRK" + + def test_adjustments_return_expected_string(self) -> None: + assert ( + str(self.test_adjustment) + == "Adjustment #-1: TINA adjusted by 1 due to BIRTH on 524990800." + ) + + def test_adjustments_return_expected_dictionary(self) -> None: + assert vars(self.test_adjustment) == { + "table": "inventory", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "SRK", + "adjustment_date": 524990800, + "event_code": "BIRTH", + "medication_code": "TINA", + "amount": 1, + "reference_id": "Tina's Mom", + "reporting_period_id": -36, + } diff --git a/tests/unit/items/events_test.py b/tests/unit/items/events_test.py new file mode 100644 index 0000000..09e1fa7 --- /dev/null +++ b/tests/unit/items/events_test.py @@ -0,0 +1,72 @@ +"""Unit tests the Events Module. + +Classes: + + Test_Event: Unit tests the Event Class. +""" + +from narcotics_tracker.items.events import Event + + +class Test_Event: + """Unit tests the Event Class. + + Behaviors Tested: + - Events class can be accessed. + - Events return expected id. + - Events return expected event_code. + - Events return expected event_name. + - Events return expected description. + - Events return expected modifier. + - Events return expected string. + - Events return expected dictionary. + """ + + test_event = Event( + table="events", + id=-1, + event_code="test_event", + event_name="Test Event", + description="An event used for testing.", + modifier=999, + created_date=1666061200, + modified_date=1666061200, + modified_by="SRK", + ) + + def test_event_class_can_be_accessed(self) -> None: + assert Event.__doc__ != None + + def test_event_returns_expected_id(self) -> None: + assert self.test_event.id == -1 + + def test_event_returns_expected_event_code(self) -> None: + assert self.test_event.event_code == "test_event" + + def test_event_returns_expected_event_name(self) -> None: + assert self.test_event.event_name == "Test Event" + + def test_event_returns_expected_description(self) -> None: + assert self.test_event.description == "An event used for testing." + + def test_event_returns_expected_modifier(self) -> None: + assert self.test_event.modifier == 999 + + def test_event_return_expected_string(self) -> None: + assert ( + str(self.test_event) + == "Event #-1: Test Event (test_event) An event used for testing." + ) + + def test_event_return_expected_dictionary(self) -> None: + assert vars(self.test_event) == { + "table": "events", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "SRK", + "event_code": "test_event", + "event_name": "Test Event", + "description": "An event used for testing.", + "modifier": 999, + } diff --git a/tests/unit/items/medications_test.py b/tests/unit/items/medications_test.py new file mode 100644 index 0000000..0c47154 --- /dev/null +++ b/tests/unit/items/medications_test.py @@ -0,0 +1,90 @@ +"""Unit tests the Medications Module. + +Classes: + + Test_Medications: Unit tests the Medication Class. +""" + +from narcotics_tracker.items.medications import Medication + + +class Test_Medications: + """Unit tests the Medication Class. + + Behaviors Tested: + - Medications class can be accessed. + - Medications return expected id. + - Medications return expected medication_code. + - Medications return expected medication_name. + - Medications return expected fill_amount. + - Medications return expected medication_amount. + - Medications return expected preferred_unit. + - Medications return expected concentration. + - Medications return expected status. + - Medications return expected string. + - Medications return expected dictionary. + """ + + test_medication = Medication( + table="medications", + id=-1, + medication_code="apap", + medication_name="Acetaminophen", + fill_amount=10, + medication_amount=1, + preferred_unit="dg", + concentration=0.1, + status="unknown", + created_date=1666061200, + modified_date=1666061200, + modified_by="SRK", + ) + + def test_medication_class_can_be_accessed(self) -> None: + assert Medication.__doc__ != None + + def test_medications_return_expected_id(self) -> None: + assert self.test_medication.id == -1 + + def test_medications_return_expected_medication_code(self) -> None: + assert self.test_medication.medication_code == "apap" + + def test_medications_return_expected_name(self) -> None: + assert self.test_medication.medication_name == "Acetaminophen" + + def test_medications_return_expected_fill_amount(self): + assert self.test_medication.fill_amount == 10 + + def test_medications_return_expected_medication_amount(self): + assert self.test_medication.medication_amount == 1 + + def test_medications_return_expected_preferred_unit(self): + assert self.test_medication.preferred_unit == "dg" + + def test_medications_return_expected_concentration(self): + assert self.test_medication.concentration == 0.1 + + def test_medications_return_expected_status(self): + assert self.test_medication.status == "unknown" + + def test_medications_return_expected_string(self) -> None: + assert ( + str(self.test_medication) + == "Medication #-1: Acetaminophen (apap) 1 dg in 10 ml." + ) + + def test_medications_return_expected_dictionary(self) -> None: + assert vars(self.test_medication) == { + "table": "medications", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "SRK", + "medication_code": "apap", + "medication_name": "Acetaminophen", + "fill_amount": 10, + "medication_amount": 1, + "preferred_unit": "dg", + "concentration": 0.1, + "status": "unknown", + } diff --git a/tests/unit/items/reporting_periods_test.py b/tests/unit/items/reporting_periods_test.py new file mode 100644 index 0000000..6a261eb --- /dev/null +++ b/tests/unit/items/reporting_periods_test.py @@ -0,0 +1,66 @@ +"""Unit tests the Reporting Periods Module. + +Classes: + + Test_ReportingPeriod: Unit tests the ReportingPeriod Class. +""" + +from narcotics_tracker.items.reporting_periods import ReportingPeriod + + +class Test_ReportingPeriod: + """Unit tests the ReportingPeriod Class. + + Behaviors Tested: + - ReportingPeriods class can be accessed. + - ReportingPeriods return expected id. + - ReportingPeriods return expected start_date. + - ReportingPeriods return expected end_date. + - ReportingPeriods return expected status. + - ReportingPeriods return expected string. + - ReportingPeriods return expected dictionary. + """ + + test_period = ReportingPeriod( + table="reporting_periods", + id=-1, + start_date=1666061200, + end_date=1666061200, + status="unfinished", + created_date=1666061200, + modified_date=1666061200, + modified_by="SRK", + ) + + def test_reportingperiod_class_can_be_accessed(self) -> None: + assert ReportingPeriod.__doc__ != None + + def test_period_returns_expected_id(self) -> None: + assert self.test_period.id == -1 + + def test_period_returns_expected_start_date(self) -> None: + assert self.test_period.start_date == 1666061200 + + def test_period_returns_expected_end_date(self) -> None: + assert self.test_period.end_date == 1666061200 + + def test_period_returns_expected_status(self) -> None: + self.test_period.status == "unfinished" + + def test_periods_return_expected_string(self) -> None: + assert ( + str(self.test_period) + == "Reporting Period #-1: Start Date: 1666061200, End Date: 1666061200, Current Status: unfinished." + ) + + def test_periods_return_expected_dictionary(self) -> None: + assert vars(self.test_period) == { + "table": "reporting_periods", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "SRK", + "start_date": 1666061200, + "end_date": 1666061200, + "status": "unfinished", + } diff --git a/tests/unit/items/statuses_test.py b/tests/unit/items/statuses_test.py new file mode 100644 index 0000000..9cd6a8e --- /dev/null +++ b/tests/unit/items/statuses_test.py @@ -0,0 +1,66 @@ +"""Unit tests the Statuses Module. + +Classes: + + Test_Status: Unit tests the Status Class. +""" + +from narcotics_tracker.items.statuses import Status + + +class Test_Status: + """Unit tests the Status Class. + + Behaviors Tested: + - Statuses class can be accessed. + - Statuses return expected id. + - Statuses return expected status_code. + - Statuses return expected status_name. + - Statuses return expected description. + - Statuses return expected string. + - Statuses return expected dictionary. + """ + + test_status = Status( + table="statuses", + id=-1, + status_code="BROKEN", + status_name="Broken", + description="Used for testing purposes.", + created_date=1666061200, + modified_date=1666061200, + modified_by="Systems", + ) + + def test_status_class_can_be_accessed(self) -> None: + assert Status.__doc__ != None + + def test_status_returned_expected_id(self) -> None: + assert self.test_status.id == -1 + + def test_statuses_return_expected_status_code(self) -> None: + assert self.test_status.status_code == "BROKEN" + + def test_statuses_return_expected_status_name(self) -> None: + assert self.test_status.status_name == "Broken" + + def test_statuses_return_expected_description(self) -> None: + assert self.test_status.description == "Used for testing purposes." + + def test_adjustments_return_expected_string(self) -> None: + assert ( + str(self.test_status) + == "Status #-1: Broken (BROKEN) Used for testing purposes." + ) + + def test_adjustments_return_expected_dictionary(self) -> None: + assert vars(self.test_status) == { + "table": "statuses", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "Systems", + "status_code": "BROKEN", + "status_name": "Broken", + "description": "Used for testing purposes.", + } diff --git a/tests/unit/items/units_test.py b/tests/unit/items/units_test.py new file mode 100644 index 0000000..4226f8c --- /dev/null +++ b/tests/unit/items/units_test.py @@ -0,0 +1,63 @@ +"""Unit tests the Units Module. + +Classes: + + Test_Units: Unit tests the Unit Class. +""" + +from narcotics_tracker.items.units import Unit + + +class Test_Unit: + """Unit tests the Unit Class. + + Behaviors Tested: + - Units class can be accessed. + - Units return expected id. + - Units return expected unit_code. + - Units return expected unit_name. + - Units return expected decimals. + - Units return expected string. + - Units return expected dictionary. + """ + + test_unit = Unit( + table="units", + id=-1, + unit_code="dg", + unit_name="Decagrams", + decimals=7, + created_date=1666061200, + modified_date=1666061200, + modified_by="System", + ) + + def test_unit_class_can_be_accessed(self) -> None: + assert Unit.__doc__ != None + + def test_units_return_expected_id(self) -> None: + assert self.test_unit.id == -1 + + def test_units_return_expected_code(self) -> None: + assert self.test_unit.unit_code == "dg" + + def test_units_return_expected_name(self) -> None: + assert self.test_unit.unit_name == "Decagrams" + + def test_units_return_expected_decimals(self) -> None: + assert self.test_unit.decimals == 7 + + def test_units_return_expected_string(self) -> None: + assert str(self.test_unit) == "Unit #-1: Decagrams (dg)." + + def test_units_return_expected_dictionary(self) -> None: + assert vars(self.test_unit) == { + "table": "units", + "id": -1, + "created_date": 1666061200, + "modified_date": 1666061200, + "modified_by": "System", + "unit_code": "dg", + "unit_name": "Decagrams", + "decimals": 7, + } diff --git a/tests/unit/medication_test.py b/tests/unit/medication_test.py deleted file mode 100644 index e07cb33..0000000 --- a/tests/unit/medication_test.py +++ /dev/null @@ -1,509 +0,0 @@ -"""Contains Test_MedicationAttributes and Test_MedicationMethods classes. - -Classes: - - Test_Medication Module: Contains all unit tests for the Medication Module. - - Test_MedicationAttributes: Contains all unit tests for the attributes of the Medication Class. - - Test_MedicationMethods: Contains all unit tests for the methods of the Medication Class. - -""" - -from narcotics_tracker import database, medications - - -class Test_MedicationModule: - """Contains all unit tests for the Medication Module. - - Behaviors Tested: - - Medication module can be accessed. - - Method return_table_creation_query returns correct string. - - Method parse_medication_data creates dictionary with correct vales. - - Method return_medications returns all medications. - - Method return_preferred_unit returns the correct unit. - """ - - def test_medications_module_can_be_accessed(self) -> None: - """Tests that the medication module exists and can be accessed. - - Asserts that calling medication.__doc__ does not return 'None'. - """ - assert medications.__doc__ != None - - def test_medications_table_query_returns_correct_string(self): - """Tests that return_table_creation_query returns correct string. - - Calls medication.return_table_creation_query - - Asserts that return_table_create_query is - 'CREATE TABLE IF NOT EXISTS medications ( - MEDICATION_ID INTEGER PRIMARY KEY, - MEDICATION_CODE TEXT UNIQUE, - NAME TEXT, - CONTAINER_TYPE TEXT, - FILL_AMOUNT REAL, - DOSE_IN_MCG REAL, - PREFERRED_UNIT TEXT, - CONCENTRATION REAL, - STATUS TEXT, - CREATED_DATE INT, - MODIFIED_DATE INT, - MODIFIED_BY TEXT, - FOREIGN KEY (PREFERRED_UNIT) REFERENCES units (unit_code) ON UPDATE CASCADE, - FOREIGN KEY (CONTAINER_TYPE) REFERENCES containers (container_code) ON UPDATE CASCADE, - FOREIGN KEY (STATUS) REFERENCES statuses (status_code) ON UPDATE CASCADE - )' - """ - assert medications.return_table_creation_query() == ( - """CREATE TABLE IF NOT EXISTS medications ( - MEDICATION_ID INTEGER PRIMARY KEY, - MEDICATION_CODE TEXT UNIQUE, - NAME TEXT, - CONTAINER_TYPE TEXT, - FILL_AMOUNT REAL, - DOSE_IN_MCG REAL, - PREFERRED_UNIT TEXT, - CONCENTRATION REAL, - STATUS TEXT, - CREATED_DATE INT, - MODIFIED_DATE INT, - MODIFIED_BY TEXT, - FOREIGN KEY (PREFERRED_UNIT) REFERENCES units (unit_code) ON UPDATE CASCADE, - FOREIGN KEY (CONTAINER_TYPE) REFERENCES containers (container_code) ON UPDATE CASCADE, - FOREIGN KEY (STATUS) REFERENCES statuses (status_code) ON UPDATE CASCADE - )""" - ) - - def test_parse_medication_data_creates_dictionary_with_correct_values( - self, test_medication - ): - """Tests that parse_medication_data returns correct dictionary data. - - Loads test_medication and saves to database. Retrieves medication data from - database and parses it. - - Asserts that the data returned matches ALL expected values. - """ - test_medication = test_medication - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - test_medication.save(db) - - code = ["Un-69420-9001"] - raw_data = db.return_data( - """SELECT * FROM medications WHERE medication_code=(?)""", code - ) - - med_data = medications.parse_medication_data(raw_data) - - assert ( - med_data["medication_id"] == 1 - and med_data["name"] == "Unobtanium" - and med_data["medication_code"] == "Un-69420-9001" - and med_data["container_type"] == "Vial" - and med_data["fill_amount"] == 9_001.0 - and med_data["dose"] == 69_420.0 - and med_data["unit"] == "mg" - and med_data["concentration"] == 7.712476391512054 - and med_data["status"] == "Discontinued" - ) - - def test_return_medication_returns_expected_medication( - self, test_medication, reset_database - ) -> None: - """Tests that the return_medication method returns the expected medication. - - Loads and saves test_medication. Creates and save. 2nd_med - Calls medication.return_medication(). - - Asserts that medication.return_medication() returns expected data. - """ - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - - test_medication = test_medication - second_med = test_medication - second_med.code = "SECOND MED" - test_medication.save(db) - second_med.save(db) - - medication_list = medications.return_medications(db) - - assert ( - "Unobtanium 69.42 mg in 9001.0 ml. Code: Un-69420-9001." - in medication_list[0] - ) - - def test_return_preferred_unit_returns_correct_unit_as_string( - self, reset_database, test_medication - ) -> None: - """Tests that the correct unit is returned for the specified med. - - Loads and saves test_medication to database. Calls - medication.return_preferred_unit(test_medication, db). - - Asserts that 'mg' is returned.""" - with database.Database("test_database_2.db") as db: - - test_medication = test_medication - test_medication.medication_code = "morphine" - - assert medications.return_preferred_unit( - test_medication.medication_code, db - ) - - -class Test_MedicationAttributes: - """Contains all unit tests for the attributes of the Medication Class. - - Behaviors Tested: - - Medications can be created. - - Medications return expected medication_ID. - - Medications return expected code. - - Medications return expected name. - - Medications return expected container_type. - - Medications return expected fill_amount. - - Medications return expected dose. - - Medications return expected preferred_unit. - - Medications return expected concentration. - - Medications return expected created_date. - - Medications return expected modified_date. - - Medications return expected modified_by. - - Medications can be edited. - """ - - def test_medications_can_be_created(self, test_medication): - """Tests that Medication object can be created. - - Loads test_medication. - - Asserts that test_medication is an instance of the medication.Medication - Class. - """ - test_medication = test_medication - - assert isinstance(test_medication, medications.Medication) - - def test_medications_return_expected_medication_id(self, test_medication): - """Tests that the medication_id is returned as expected. - - Loads test_medication. - - Asserts that test_medication.medication_id equals '1'. - """ - test_medication = test_medication - - assert test_medication.medication_id == 1 - - def test_medications_return_expected_code(self, test_medication): - """Tests that the medication's code is returned as expected. - - Loads test_medication. - - Asserts that test_medication.code equals 'Un-69420-9001'. - """ - test_medication = test_medication - - assert test_medication.medication_code == "Un-69420-9001" - - def test_medications_return_expected_name(self, test_medication): - """Tests that the medication's code is returned as expected. - - Loads test_medication. - - Asserts that test_medication.name equals 'Unobtanium'. - """ - test_medication = test_medication - - assert test_medication.name == "Unobtanium" - - def test_medications_return_expected_container_type(self, test_medication): - """Tests that the medication's code is returned as expected. - - Loads test_medication. - - Asserts that test_medication.container_type equals - 'Vial'. - """ - test_medication = test_medication - - assert test_medication.container_type == "Vial" - - def test_medications_return_expected_fill_amount(self, test_medication): - """Tests that the medication's fill amount is returned correctly. - - Loads test_medication. - - Asserts that test_medication.fill_amount equals '9001'. - """ - test_medication = test_medication - - assert test_medication.fill_amount == 9_001 - - def test_medications_return_expected_dose(self, test_medication): - """Tests that the medication's dose is returned correctly. - - Loads test_medication. - - Asserts that test_medication.dose equals '69420'. - """ - test_medication = test_medication - - assert test_medication.dose == 69_420 - - def test_medications_return_expected_preferred_unit(self, test_medication): - """Tests that the medication's preferred unit is returned correctly. - - Loads test_medication. - - Asserts that test_medication.preferred_unit equals 'units.Unit.MCG'. - """ - test_medication = test_medication - - assert test_medication.preferred_unit == "mg" - - def test_medications_return_expected_concentration(self, test_medication): - """Tests that the medication's concentration is returned correctly. - - Loads test_medication. - - Asserts that test_medication.concentration equals '7.712476391512054'. - """ - test_medication = test_medication - - assert test_medication.concentration == 7.712476391512054 - - def test_medications_return_expected_created_date(self, test_medication): - """Tests that the medication's created_date is returned correctly. - - Loads test_medication. - - Asserts that test_medication.created_date equals '01-02-1986'. - """ - test_medication = test_medication - - assert test_medication.created_date == 505008000 - - def test_medications_return_expected_modified_date(self, test_medication): - """Tests that the medication's modified_date is returned correctly. - - Loads test_medication. - - Asserts that test_medication.modified_date equals '08-09-2022'. - """ - test_medication = test_medication - - assert test_medication.modified_date == 1660003200 - - def test_medications_return_expected_modified_by(self, test_medication): - """Tests that the medication's modified_by is returned correctly. - - Loads test_medication. - - Asserts that test_medication.modified_by equals 'Kvothe'. - """ - test_medication = test_medication - - assert test_medication.modified_by == "Kvothe" - - def test_medications_can_be_edited(self, test_medication): - """Tests that the medication's attributes and be changed. - - Loads test_medication. Changes preferred_unit to 'G'. - - Asserts that test_medication.preferred unit is 'G'. - """ - test_medication = test_medication - - test_medication.preferred_unit = "G" - - assert test_medication.preferred_unit == "G" - - -class Test_MedicationMethods: - """Contains all unit tests for the methods of the Medication Class. - - Behaviors Tested: - - - __repr__ returns the correct string. - - Medication data can be saved to the database. - - Medication data can be updated in the database. - - Medication data can be deleted from the database. - - return_attributes returns the correct information. - """ - - def test__repr___returns_correct_string(self, test_medication): - """Tests that __repr__ returns correct string. - - Loads test_medication. Calls str(test_medication). - - return f"{self.name} {self.dose}{self.preferred_unit} in {self.fill_amount}ml. Code: {self.medication_code}" - - Asserts that str(test_medication) returns: - "Unobtainium 69420.0mg in 9001ml. Code: Un-69420-9001." - """ - test_medication = test_medication - assert str(test_medication) == ( - "Unobtanium 69420.0mg in 9001ml. Code: Un-69420-9001." - ) - - def test_return_attributes(self, test_medication): - """Tests that the medication data is correctly returned. - - Loads test_medication. Calls test_medication.return_attributes(). - - Asserts values returned are expected values. - """ - test_medication = test_medication - assert test_medication.return_attributes() == ( - 1, - "Un-69420-9001", - "Unobtanium", - "Vial", - 9001, - 69420.0, - "mg", - 7.712476391512054, - "Discontinued", - 505008000, - 1660003200, - "Kvothe", - ) - - def test_can_write_medication_to_database(self, test_medication) -> None: - """Tests that the medication data is correctly written to - database. - - Loads test_medication. Saves to database. Calls db.return_data() on - medication. - - Asserts data return has name 'Unobtanium'. - """ - test_medication = test_medication - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - test_medication.save(db) - - data = db.return_data( - """SELECT * FROM medications WHERE MEDICATION_CODE='Un-69420-9001'""" - )[0][2] - - assert data == "Unobtanium" - - def test_can_read_medication_from_database(self, test_medication) -> None: - """Tests to see if the medication's data can be returned from database. - - Builds and saves test_medication. Calls test_medication.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - - test_medication = test_medication - test_medication.save(db) - test_medication.update(db) - - data = test_medication.read(db)[0] - expected = [ - 1, - "Un-69420-9001", - "Unobtanium", - "Vial", - 9001.0, - 69420.0, - "mg", - 7.712476391512054, - "Discontinued", - ] - - assert ( - data[0] == expected[0] - and data[1] == expected[1] - and data[2] == expected[2] - and data[3] == expected[3] - and data[4] == expected[4] - and data[5] == expected[5] - and data[6] == expected[6] - and data[7] == expected[7] - and data[8] == expected[8] - ) - - def test_delete_medication(self, test_medication, reset_database): - """Tests that the medication can be deleted from the database. - - Loads test_medication. Saves it to database. Then deletes it. Gets data from - medication table. - - Asserts data is empty. - """ - test_medication = test_medication - - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - - test_medication.save(db) - test_medication.delete(db) - - data = db.return_data("""SELECT * FROM medications""") - assert data == [] - - def test_can_update_existing_medication(self, test_medication, reset_database): - """Tests that a medication's attributes can be updated in the - database. - - Loads test_medication and saves to database. Loads medication info from - database to loaded_med. Changes loaded_med status to - 'medication_statuses.MedicationStatus.ACTIVE'. Updates medication in - database. - - Asserts medication status is - 'Active'. - """ - test_medication = test_medication - - with database.Database("test_database.db") as db: - db.create_table(medications.return_table_creation_query()) - test_medication.save(db) - - med_code = "Un-69420-9001" - loaded_med = db.load_medication(med_code) - loaded_med.status = "Active" - loaded_med.update(db) - - data = db.return_data( - """SELECT status FROM medications WHERE medication_code=(?)""", - [med_code], - ) - - assert data[0][0] == "Active" - - # def test_update_nonexisting_medication(self, test_medication, reset_database): - # """Tests that a medication's attributes can be updated in the - # database. - - # Loads test_medication and saves to database. Loads medication info from - # database to loaded_med. Changes loaded_med status to - # 'medication_statuses.MedicationStatus.ACTIVE'. Updates medication in - # database. - - # Asserts medication status is - # 'Active'. - # """ - # test_medication = test_medication - - # with database.Database("test_database.db") as db: - # db.create_table(medications.return_table_creation_query()) - - # med_code = "Un-69420-9001" - # loaded_med = db.load_medication(med_code) - # loaded_med.status = "Active" - # loaded_med.update(db, med_code) - - # data = db.return_data( - # """SELECT status FROM medications WHERE MEDICATION_CODE=(?)""", - # [med_code], - # ) - - # assert data[0][0] == "Active" diff --git a/tests/unit/reporting_periods_test.py b/tests/unit/reporting_periods_test.py deleted file mode 100644 index c62f119..0000000 --- a/tests/unit/reporting_periods_test.py +++ /dev/null @@ -1,344 +0,0 @@ -"""Contains the classes and test which test the periods module. - -Classes: - Test_PeriodsModule: Contains all unit tests for the periods module. - - Test_PeriodAttributes: Contains all unit tests for Period's attributes. - - Test_PeriodMethods: Contains all unit tests for the Period Class' methods. -""" - -from narcotics_tracker import database, reporting_periods - - -class Test_PeriodsModule: - """Contains all unit tests for the periods module. - - Behaviors Tested: - - Periods module can be accessed. - - Method return_table_creation_query returns correct string. - - Method return_periods returns all reporting_periods. - """ - - def test_periods_module_can_be_accessed(self) -> None: - """Tests that the periods module exists and can be accessed. - - Asserts that calling periods.__doc__ does not return 'None'. - """ - assert reporting_periods.__doc__ != None - - def test_return_table_creation_query_returns_expected_string(self) -> None: - """Tests that the table_creation_query returns the correct string. - - Calls periods.return_table_creation_query(). - - Asserts that expected_query is returned. - """ - expected_query = """CREATE TABLE IF NOT EXISTS reporting_periods ( - PERIOD_ID INTEGER PRIMARY KEY, - STARTING_DATE INTEGER, - ENDING_DATE INTEGER, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - assert reporting_periods.return_table_creation_query() == expected_query - - def test_return_periods_returns_expected_reporting_periods( - self, test_period, reset_database - ) -> None: - """Tests that the show method returns the expected reporting periods. - - Loads and saves test_period. Calls periods.show(). - - Asserts that periods.show returns expected data. - """ - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - - test_period = test_period - test_period.save(db) - periods_list, _ = reporting_periods.return_periods(db) - assert ( - "Reporting Period 9001. Started on: 2000-12-31 19:00:00. Ends on: 2100-06-29 20:00:00" - in periods_list - ) - - def test_parse_reporting_period_data_returns_correct_values( - self, reset_database, test_period - ) -> None: - """Tests if parse_reporting_period_data returns correct dictionary. - - Resets the database. Creates reporting_periods table. Builds and saves - test_period to database. Queries database for reporting period data - and calls the parser. - - Asserts that dictionary returned assigns the correct data to correct - keys. - """ - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - - test_period = test_period - test_period.save(db) - data = test_period.read(db) - dictionary = reporting_periods.parse_reporting_period_data(data) - - assert ( - dictionary["period_id"] == 9001 - and dictionary["starting_date"] - == database.return_datetime("2001-01-01 00:00:00") - and dictionary["ending_date"] - == database.return_datetime("2100-06-30 00:00:00") - ) - - -class Test_ReportingPeriodAttributes: - """Contains all unit tests for the Period Class' attributes. - - Behaviors Tested: - - Period class can be accessed. - - Period objects can be created. - - period_id attribute returns correct value. - - starting_date attribute returns correct value. - - ending_date attribute returns correct value. - - created_date attribute returns correct value. - - modified_date attribute returns correct value. - - modified_by attribute returns correct value. - """ - - def test_period_class_can_be_accessed(self) -> None: - """Tests that the Period Class exists and can be accessed. - - Asserts that calling Period.__doc__ does not return 'None'. - """ - assert reporting_periods.ReportingPeriod.__doc__ != None - - def test_can_create_period_objects(self, test_period) -> None: - """Tests that objects can be created from the Period Class. - - Loads test_period. - - Asserts that test_period is an instance of the Period Class. - """ - test_period = test_period - - assert isinstance(test_period, reporting_periods.ReportingPeriod) - - def test_period_id_returns_correct_value(self, test_period) -> None: - """Tests that the period_id attribute returns the correct value. - - Loads test_period. Sets the period_id to '9001'. - - Asserts test_period.period_id is '9001'. - """ - test_period = test_period - test_period.period_id = 9001 - - assert test_period.period_id == 9001 - - def test_starting_date_returns_correct_value(self, test_period) -> None: - """Tests that the starting_date attribute returns the correct value. - - Loads test_period. - - Asserts that test_period.starting_date is '978307200'. - """ - test_period = test_period - - assert test_period.starting_date == 978307200 - - def test_ending_date_returns_correct_value(self, test_period) -> None: - """Tests that the ending_date attributes returns the correct value. - - Loads test_period. - - Asserts that test_period.ending_date is '4117996800' - """ - test_period = test_period - - assert test_period.ending_date == 4117996800 - - def test_created_date_returns_correct_value(self, test_period) -> None: - """Tests that the created_date attributes returns the correct value. - - Loads test_period. - - Asserts that test_period.created_date is '1659312000' - """ - test_period = test_period - - assert test_period.created_date == 1659312000 - - def test_modified_date_returns_correct_value(self, test_period) -> None: - """Tests that the modified_date attributes returns the correct value. - - Loads test_period. - - Asserts that test_period.modified_date is '1659312000' - """ - test_period = test_period - - assert test_period.modified_date == 1659312000 - - def test_modified_by_returns_correct_value(self, test_period) -> None: - """Tests that the modified_by attributes returns the correct value. - - Loads test_period. - - Asserts that test_period.modified_by is 'Cinder' - """ - test_period = test_period - - assert test_period.modified_by == "Cinder" - - -class Test_ReportingPeriodMethods: - """Contains all unit tests for the Period Class' methods. - - Behaviors Tested: - - __init__ sets attributes correctly. - - __repr__ returns correct string. - - Can save ReportingPeriod to database. - - Can read ReportingPeriod data from database. - - Can load ReportingPeriod from database. - - Can update ReportingPeriod in database. - - Can delete reporting period from database. - - return_attributes returns the correct values. - """ - - def test___init___sets_attributes_correctly(self, test_period) -> None: - """Tests the initializer sets the objects attributes correctly. - - Loads test_period. - - Asserts that period_id, starting_date, and ending_date attributes are - set to the expected values. - """ - test_period = test_period - - assert ( - test_period.period_id == 9001 - and test_period.starting_date == 978307200 - and test_period.ending_date == 4117996800 - ) - - def test___repr___returns_expected_string(self, test_period) -> None: - """Tests that __repr__ returns correct string. - - Loads test_period. Calls str(test_period). - - Asserts that str(test_med) returns: - " - """ - test_period = test_period - - assert str(test_period) == ( - "Reporting Period 9001. Started on: 2000-12-31 19:00:00. Ends on: 2100-06-29 20:00:00." - ) - - def test_can_save_reporting_period_to_database( - self, test_period, reset_database - ) -> None: - """Tests that reporting periods can be saved to the database. - - Loads test_period. Calls test_period.save. Calls db.return_data() - using the period_id of '9001'. - - Asserts that returned data has starting_date value of '02-29-0001'. - """ - test_period = test_period - - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - - test_period.save(db) - - data = db.return_data( - """SELECT starting_date FROM reporting_periods WHERE period_id = '9001'""" - ) - - assert data[0][0] == 978307200 - - def test_can_read_reporting_period_from_database( - self, reset_database, test_period - ) -> None: - """Tests if the reporting period's data can be returned from database. - - Resets the database. Creates reporting_periods table. Builds and saves - test_period. Calls test_period.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - - test_period = test_period - test_period.save(db) - - data = test_period.read(db)[0] - expected = [ - 9001, - database.return_datetime("2001-01-01 00:00:00"), - database.return_datetime("2100-06-30 00:00:00"), - ] - - assert ( - data[0] == expected[0] and data[1] == expected[1] and data[2] == expected[2] - ) - - def test_can_load_reporting_period_from_database( - self, reset_database, test_period - ) -> None: - """Tests to see if a Reporting Period Object can be loaded from data. - Loads and saves test_period. Creates loaded_period from data. - - Asserts that test_period and loaded_period return identical - attributes. - """ - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - - test_period = test_period - test_period.save(db) - - loaded_period_type = db.load_reporting_period(9001) - - def test_return_attributes(self, test_period): - """Tests that the reporting period data is correctly returned. - - Loads test_period. Calls test_period.return_attributes(). - - Asserts values returned are expected values. - """ - test_period = test_period - assert test_period.return_attributes() == ( - 9001, - 978307200, - 4117996800, - 1659312000, - 1659312000, - "Cinder", - ) - - def test_can_delete_reporting_period_from_database( - self, test_period, reset_database - ): - """Tests that reporting periods can be deleted from the database. - - Loads test_period. Saves it to database. Then deletes it. Gets data from - reporting_periods table. - - Asserts data is empty. - """ - test_period = test_period - - with database.Database("test_database.db") as db: - db.create_table(reporting_periods.return_table_creation_query()) - - test_period.save(db) - test_period.delete(db) - - data = db.return_data("""SELECT * FROM reporting_periods""") - assert data == [] diff --git a/tests/unit/save_item_commands_test.py b/tests/unit/save_item_commands_test.py new file mode 100644 index 0000000..44ba9a5 --- /dev/null +++ b/tests/unit/save_item_commands_test.py @@ -0,0 +1,46 @@ +"""Contains the unit tests for the Commands Module. + +Classes: + Test_AddMedication: Unit tests the AddMedication command. +""" + + +from typing import TYPE_CHECKING + +import pytest + +from narcotics_tracker.builders.medication_builder import MedicationBuilder +from narcotics_tracker.commands import AddMedication + +if TYPE_CHECKING: + from narcotics_tracker.items.medications import Medication + + +@pytest.fixture +def test_med() -> "Medication": + med_builder = ( + MedicationBuilder() + .set_table("medications") + .set_id(-1) + .set_created_date(1666061200) + .set_modified_date(1666061200) + .set_modified_by("SRK") + .set_medication_code("apap") + .set_medication_name("Acetaminophen") + .set_fill_amount(10) + .set_medication_amount(1) + .set_preferred_unit("dg") + .set_concentration() + .set_status("unknown") + ) + + return med_builder.build() + + +class Test_AddMedication: + """Unit tests the AddMedication command. + + Behaviors Tested: + - DataItem information is extracted correctly. + - Table name is extracted correctly. + """ diff --git a/tests/unit/services/__init__.py b/tests/unit/services/__init__.py new file mode 100644 index 0000000..033892b --- /dev/null +++ b/tests/unit/services/__init__.py @@ -0,0 +1,14 @@ +"""Handles unit testing of the Services Package. + +Modules: + conversion_manager_test: Contains tests for the Conversion Manager Module. + + datetime_manager_test: Contains unit tests for the datetime Manager Module. + + service_provider_test: Handles unit testing of the service provider module. + + sqlite_manager_test: Contains classes to test the SQLite Manager Module. + + + +""" diff --git a/tests/unit/services/conversion_manager_test.py b/tests/unit/services/conversion_manager_test.py new file mode 100644 index 0000000..87bce4c --- /dev/null +++ b/tests/unit/services/conversion_manager_test.py @@ -0,0 +1,84 @@ +"""Contains tests for the Conversion Manager Module. + +Classes: +""" + +from narcotics_tracker.services.conversion_manager import ConversionManager + + +class Test_ConversionManager: + """Unit tests the ConversionManager. + + Behaviors Tested: + + - Converts from grams to the standard unit. + + - Converts from milligrams to the standard unit. + + - Converts from micrograms to the standard unit. + + - Converts from the standard unit to grams. + + - Converts from the standard unit to milligrams. + + - Converts from the standard unit to micrograms. + + - Converts from grams to milliliters. + + - Converts from milligrams to milliliters. + + - Converts from micrograms to milliliters. + """ + + +def test_convert_grams_to_standard() -> None: + answer = ConversionManager().to_standard(1, "g") + + assert answer == 100000000 + + +def test_convert_milligrams_to_standard() -> None: + answer = ConversionManager().to_standard(1, "mg") + + assert answer == 100000 + + +def test_convert_micrograms_to_standard() -> None: + answer = ConversionManager().to_standard(1, "mcg") + assert answer == 100 + + +def test_convert_standard_to_gram(): + answer = ConversionManager().to_preferred(100000000, "g") + + assert answer == 1 + + +def test_convert_standard_to_milligrams(): + answer = ConversionManager().to_preferred(100000, "mg") + + assert answer == 1 + + +def test_converts_standard_to_micrograms(): + answer = ConversionManager().to_preferred(100, "mcg") + + assert answer == 1 + + +def test_conversion_g_to_milliliters(): + answer = ConversionManager().to_milliliters(1500000000, "g", 5) + + assert answer == 3 + + +def test_conversion_mg_to_milliliters(): + answer = ConversionManager().to_milliliters(1000000, "mg", 5) + + assert answer == 2 + + +def test_conversion_mcg_to_milliliters(): + answer = ConversionManager().to_milliliters(10000, "mcg", 50) + + assert answer == 2 diff --git a/tests/unit/services/datetime_manager_test.py b/tests/unit/services/datetime_manager_test.py new file mode 100644 index 0000000..1c310d3 --- /dev/null +++ b/tests/unit/services/datetime_manager_test.py @@ -0,0 +1,88 @@ +"""Contains unit tests for the datetime Manager Module. + +Classes: + Test_DateTimeManager: Unit tests the DateTimeManager class. +""" + + +import datetime + +import pendulum + +from narcotics_tracker.services.datetime_manager import DateTimeManager + + +class Test_DateTimeManager: + """Unit tests the DateTimeManager class. + + Behaviors Tested: + - Can return correct current datetime. + + - Can return current datetime as a timestamp. + + - Can convert string to timestamp. + + - Can convert timestamp to string. + + - _date_is_invalid returns True when None is passed. + + - _date_is_invalid returns True when a string is passed. + + - _date_is_invalid returns False when int is passed. + """ + + def test_DateTimeManager_can_return_current_datetime(self) -> None: + pdt = DateTimeManager() + assert pdt._current_datetime() != datetime.datetime.now() + + def test_DateTimeManager_can_return_current_datetime_as_timestamp(self) -> None: + pdt = DateTimeManager() + assert type(pdt.return_current()) == int + + def test_DateTimeManager_can_convert_string_to_timestamp(self) -> None: + pdt = DateTimeManager() + assert pdt.convert_to_timestamp("01-02-1986 14:10:00") == 505077000 + + def test_DateTimeManager_can_convert_timestamp_to_string(self) -> None: + pdt = DateTimeManager() + + assert pdt.convert_to_string(505077000) == "01-02-1986 14:10:00" + + def test_assign_datetime_returns_current_timestamp(self) -> None: + pdt = DateTimeManager() + assert pdt._assign(None) == pdt._current_datetime().int_timestamp + + def test_assign_datetime_converts_string_to_timestamp(self) -> None: + pdt = DateTimeManager() + assert pdt._assign("12-31-1969 19:00:01") == 1 + + def test_assign_datetime_passes_integer(self) -> None: + pdt = DateTimeManager() + assert pdt._assign(12) == 12 + + def test_date_is_invalid_returns_true_when_None_is_passed(self) -> None: + assert DateTimeManager()._date_is_invalid(None) == True + + def test_date_is_invalid_returns_true_when_string_is_passed(self) -> None: + assert DateTimeManager()._date_is_invalid("Hello") == True + + def test_date_is_invalid_returns_false_when_int_is_passed(self) -> None: + assert DateTimeManager()._date_is_invalid(123) == False + + def test_assign_datetime_returns_current_datetime_when_none_passed(self) -> None: + assert DateTimeManager()._assign(None) == pendulum.now().int_timestamp + + def test_assign_datetime_returns_correct_timestamp_when_string_passed(self) -> None: + assert DateTimeManager()._assign("01-02-1986 14:10:00") == 505077000 + + def test_assign_datetime_returns_initial_value_when_integer(self) -> None: + assert DateTimeManager()._assign(123) == 123 + + def test_validate_date_returns_initial_value_when_int_passed(self) -> None: + assert DateTimeManager().validate(123) == 123 + + def test_validate_date_returns_converted_timestamp_when_string_passed(self) -> None: + assert DateTimeManager().validate("01-02-1986 14:10:00") == 505077000 + + def test_validate_date_returns_current_datetime_when_None_passed(self) -> None: + assert DateTimeManager().validate(None) == pendulum.now().int_timestamp diff --git a/tests/unit/services/sqlite_manager_test.py b/tests/unit/services/sqlite_manager_test.py new file mode 100644 index 0000000..af2ec3f --- /dev/null +++ b/tests/unit/services/sqlite_manager_test.py @@ -0,0 +1,104 @@ +"""Contains classes to test the SQLite Manager Module. + +Classes: + + Test_SQLiteManager: Tests the SQLiteManager class. + +""" + +import os + +from narcotics_tracker.services.sqlite_manager import SQLiteManager + + +class Test_SQLiteManager: + """Tests the SQLiteManager class. + + SQLiteManager Behaviors Tested: + - Can be instantiated. + - Can create a database file. + - Can connect to a database. + - Can delete database files. + - Can create tables. + - Can add data. + - Can delete data. + - Can order returned data. + - Can update data. + """ + + def test_SQLiteManager_object_can_be_instantiated(self): + db = SQLiteManager("test_database.db") + + assert isinstance(db, SQLiteManager) + + def test_SQLiteManager_can_create_database_file(self, reset_database): + db = SQLiteManager("test.db") + assert os.path.exists("data/test.db") + db.delete_database() + + def test_SQLiteManager_can_connect_to_database(self, reset_database): + db = SQLiteManager("test_database.db") + assert db.connection is not None + + def test_SQLiteManager_can_delete_database_file(self, reset_database): + db = SQLiteManager("test_database.db") + db.delete_database() + + assert os.path.exists("data/test_database.db") == False + + def test_SQLiteManager_can_create_tables(self, reset_database): + db = SQLiteManager("test_database.db") + db.create_table("test_table", {"data": "TEXT NOT NULL"}) + + db = SQLiteManager("test_database.db") + cursor = db._execute("""SELECT name FROM sqlite_master WHERE type = 'table'""") + table_name = cursor.fetchall()[0][0] + + assert table_name == "test_table" + + def test_SQLiteManager_can_add_data(self, reset_database): + db = SQLiteManager("test_database.db") + db.create_table("test_table", {"data": "TEXT"}) + + db.add("test_table", {"data": "Hello"}) + + cursor = db.read("test_table") + data = cursor.fetchall()[0][0] + + assert data == "Hello" + + def test_SQLiteManager_can_delete_data(self, reset_database): + db = SQLiteManager("test_database.db") + db.create_table("test_table", {"data": "TEXT"}) + db.add("test_table", {"data": "Hello"}) + + db.remove("test_table", {"data": "Hello"}) + + cursor = db.read("test_table") + data = cursor.fetchall() + + assert data == [] + + def test_SQLiteManager_can_order_returned_data(self, reset_database): + db = SQLiteManager("test_database.db") + db.create_table("test_table", {"number": "INTEGER"}) + db.add("test_table", {"number": "17"}) + db.add("test_table", {"number": "1"}) + db.add("test_table", {"number": "99999999"}) + db.add("test_table", {"number": "8211986"}) + + cursor = db.read("test_table", order_by="number") + data = cursor.fetchall() + + assert data == [(1,), (17,), (8211986,), (99999999,)] + + def test_SQLiteManager_can_update_data(self, reset_database): + db = SQLiteManager("test_database.db") + db.create_table("test_table", {"number": "INTEGER", "word": "TEXT"}) + db.add("test_table", {"number": 17, "word": "Cow"}) + + db.update("test_table", {"number": 7, "word": "Pig"}, {"number": 17}) + + cursor = db.read("test_table") + results = cursor.fetchall() + assert results == [(7, "Pig")] diff --git a/tests/unit/setup_script_test.py b/tests/unit/setup_script_test.py deleted file mode 100644 index 6b0edc2..0000000 --- a/tests/unit/setup_script_test.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Contains the Test_Setup class and all tests for the setup module. - -Classes: - Test_Setup: Contains all unit tests for the setup module.""" - -import os - -from narcotics_tracker import database -from scripts import setup - - -class Test_Setup: - """Contains all unit tests for the setup module. - - Behaviors Tested: - - Setup can create database file. - - Setup can create medication table. - """ - - def test_setup_can_create_database_file(self, reset_database): - """Tests to see if the database file can be created. - - Connects to 'test_database.db'. - - Asserts that 'data/test_database.db' exists. - """ - with database.Database("test_database.db") as db: - - assert os.path.exists("data/test_database.db") - - def test_setup_can_create_medications_table(self, reset_database): - """Tests to see if the medication table can be created. - - Connects to 'test_database.db'. Creates medication table. Returns - table names. - - Asserts that 'medications' is in list of table names. - """ - with database.Database("test_database.db") as db: - - setup.create_medications_table(db) - - data = db.return_table_names() - - assert "medications" in data - - def test_setup_can_create_reporting_periods_table(self, reset_database) -> None: - """Tests to see if the reporting_periods table can be created. - - Connects to 'test_database.db'. Creates reporting_periods table. - Returns table names. - - Asserts that 'reporting_period' is in list of table names. - """ - with database.Database("test_database.db") as db: - - setup.create_reporting_periods_table(db) - - data = db.return_table_names() - - assert "reporting_periods" in data - - def test_setup_can_create_event_types_table(self, reset_database) -> None: - """Tests to see if the event_types table can be created. - - Connects to 'test_database.db'. Creates event_types table. - Returns table names. - - Asserts that 'events' is in list of table names. - """ - with database.Database("test_database.db") as db: - - setup.create_events_table(db) - - data = db.return_table_names() - - assert "events" in data - - def test_setup_can_populate_events_table(self, reset_database) -> None: - """Tests that the setup script adds the standard events to the table. - - Resets the database. Creates table and calls - populate_database_with_standard_events(). Queries events table. - - Asserts that the returned data contains 6 items. - """ - with database.Database("test_database.db") as db: - setup.create_events_table(db) - setup.populate_database_with_standard_events(db) - - data = db.return_data("""SELECT event_name FROM events""") - - assert len(data) == 6 - - def test_setup_can_populate_units_table(self, reset_database) -> None: - """Tests that the setup script adds the standard units to the table. - - Resets the database. Creates table and calls - populate_database_with_standard_units(). Queries units table. - - Asserts that the returned data contains 4 items. - """ - with database.Database("test_database.db") as db: - setup.create_units_table(db) - setup.populate_database_with_standard_units(db) - - data = db.return_data("""SELECT unit_code FROM units""") - - assert len(data) == 4 diff --git a/tests/unit/setup_test.py b/tests/unit/setup_test.py index 16f6491..794ed1d 100644 --- a/tests/unit/setup_test.py +++ b/tests/unit/setup_test.py @@ -5,8 +5,8 @@ Test_SetupPackage: Contains all unit tests for the Setup Package. """ -from narcotics_tracker import setup -from narcotics_tracker.setup import standard_items +from narcotics_tracker import configuration +from narcotics_tracker.configuration import standard_items class Test_SetupPackage: @@ -22,7 +22,7 @@ def test_setup_package_exists_and_can_be_accessed(self) -> None: Asserts that setup.__doc__ does not return 'None. """ - assert setup.__doc__ != None + assert configuration.__doc__ != None class Test_StandardItemsModule: diff --git a/tests/unit/statuses_test.py b/tests/unit/statuses_test.py deleted file mode 100644 index 842b44c..0000000 --- a/tests/unit/statuses_test.py +++ /dev/null @@ -1,384 +0,0 @@ -"""Contains the classes and tests which test the statuses module. - -Classes: - - Test_StatusModule: Contains all unit tests for the statuses module. - - Test_StatusAttributes: Contains unit tests for Status' attributes. - - Test_StatusMethods: Contains unit tests for the Status' methods. -""" - -from narcotics_tracker import database, statuses - - -class Test_StatusModule: - """Contains all unit tests for the Statuses module. - - Behaviors Tested: - - Statuses module can be accessed. - - Method return_table_creation_query returns correct string. - - Method return_statuses returns all statuses. - - Method parse_unit_data returns correct dictionary and values. - """ - - def test_statuses_module_can_be_accessed(self) -> None: - """Tests that the statuses module exists and can be accessed. - - Asserts that calling statuses.__doc__ does not return 'None'. - """ - assert statuses.__doc__ != None - - def test_return_table_creation_query_returns_expected_string(self) -> None: - """Tests that the table_creation_query returns the correct string. - - Calls statuses.return_table_creation_query(). - - Asserts that expected_query is returned. - """ - expected_query = """CREATE TABLE IF NOT EXISTS statuses ( - STATUS_ID INTEGER PRIMARY KEY, - STATUS_CODE TEXT UNIQUE, - STATUS_NAME TEXT, - DESCRIPTION TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - assert statuses.return_table_creation_query() == expected_query - - def test_return_statuses_returns_expected_statuses( - self, test_status, reset_database - ) -> None: - """Tests that the return_statuses method returns the expected statuses. - - Loads and saves test_status. Creates and save. 2nd_unit - Calls statuses.return_statuses(). - - Asserts that statuses.return_statuses() returns expected data. - """ - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status = test_status - - test_status.save(db) - - statuses_list = statuses.return_statuses(db) - - assert ( - "Status -19: Active. Code: 'ACTIVE'. Used for items which are currently in use." - in statuses_list[0] - ) - - def test_parse_status_data_returns_correct_values( - self, reset_database, test_status - ) -> None: - """Tests if parse_status_data returns dictionary with correct data. - - Resets the database. Creates statuses table. Builds and saves - test_status to database. Queries database for unit data and - calls the parser. - - Asserts that dictionary returned assigns the correct data to correct - keys. - """ - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status = test_status - test_status.save(db) - data = test_status.read(db) - dictionary = statuses.parse_status_data(data) - - assert ( - dictionary["status_id"] == -19 - and dictionary["status_code"] == "ACTIVE" - and dictionary["status_name"] == "Active" - ) - - -class Test_StatusAttributes: - """Contains all unit tests for the Status Class' attributes. - - Behaviors Tested: - - Status class can be accessed. - - Status objects can be created. - - status_id attribute returns correct value. - - status_code attribute returns correct value. - - status_name attribute returns correct value. - - created_date attribute returns correct value. - - modified_date attribute returns correct value. - - modified_by attribute returns correct value. - """ - - def test_status_class_can_be_accessed(self) -> None: - """Tests that the Status Class exists and can be accessed. - - Asserts that calling Status.__doc__ does not return 'None'. - """ - assert statuses.Status.__doc__ != None - - def test_can_create_status_objects(self, test_status) -> None: - """Tests that objects can be created from the Status Class. - - Loads test_status. - - Asserts that test_status is an instance of the Status Class. - """ - test_status = test_status - - assert isinstance(test_status, statuses.Status) - - def test_status_id_returns_correct_value(self, test_status) -> None: - """Tests that the status_id attribute returns the correct value. - - Loads test_status. - - Asserts test_status.status_id is '-19'. - """ - test_status = test_status - - assert test_status.status_id == -19 - - def test_status_code_returns_correct_value(self, test_status) -> None: - """Tests that the status_code attribute returns the correct value. - - Loads test_status. - - Asserts that test_status.status_code is 'ACTIVE'. - """ - test_status = test_status - - assert test_status.status_code == "ACTIVE" - - def test_status_name_returns_correct_value(self, test_status) -> None: - """Tests that the status_name attributes returns the correct value. - - Loads test_status. - - Asserts that test_status.status_name is 'Active' - """ - test_status = test_status - - assert test_status.status_name == "Active" - - def test_status_description_returns_correct_value(self, test_status) -> None: - """Tests that the status_description attributes returns the correct value. - - Loads test_status. - - Asserts that test_status.status_description is 'Used for items which are currently in use.' - """ - test_status = test_status - - assert test_status.description == "Used for items which are currently in use." - - def test_created_date_returns_correct_value(self, test_status) -> None: - """Tests that the created_date attributes returns the correct value. - - Loads test_status. - - Asserts that test_status.created_date is '08-26-2022' - """ - test_status = test_status - - assert test_status.created_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_date_returns_correct_value(self, test_status) -> None: - """Tests that the modified_date attributes returns the correct value. - - Loads test_status. - - Asserts that test_status.modified_date is '08-01-2022' - """ - test_status = test_status - - assert test_status.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_returns_correct_value(self, test_status) -> None: - """Tests that the modified_by attributes returns the correct value. - - Loads test_status. - - Asserts that test_status.modified_by is 'Abenthy' - """ - test_status = test_status - - assert test_status.modified_by == "Abenthy" - - -class Test_StatusMethods: - """Contains all unit tests for the Status Class' methods. - - Behaviors Tested: - - __init__ sets attributes correctly. - - __repr__ returns correct string. - - Can save Unit to database. - - Can read Unit data from database. - - Can load Unit from database. - - Can update Unit in database. - - Can delete Unit from database. - - return_attributes returns the correct values. - """ - - def test___init___sets_attributes_correctly(self, test_status) -> None: - """Tests the initializer sets the objects attributes correctly. - - Loads test_status. - - Asserts that status_id, status_code, and status_name attributes are set to - the expected values. - """ - test_status = test_status - - assert ( - test_status.status_id == -19 - and test_status.status_code == "ACTIVE" - and test_status.status_name == "Active" - and test_status.description == "Used for items which are currently in use." - ) - - def test___repr___returns_expected_string(self, test_status) -> None: - """Tests that __repr__ returns correct string. - - Loads test_status. Calls str(test_status). - - Asserts that str(test_status) returns: - Status -19: Active. Code: 'ACTIVE'. Used for items which are currently in use.' - """ - test_status = test_status - - assert str(test_status) == ( - f"Status -19: Active. Code: 'ACTIVE'. Used for items which are currently in use." - ) - - def test_can_save_status_to_database(self, test_status, reset_database) -> None: - """Tests that statuses can be saved to the database. - - Loads test_status. Calls test_status.save. Calls db.return_data() - using the status_id of '821'. - - Asserts that returned data has status_code value of 'tn'. - """ - test_status = test_status - - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status.save(db) - - data = db.return_data( - """SELECT status_code FROM statuses WHERE status_id = -19""" - ) - - assert data[0][0] == "ACTIVE" - - def test_can_read_status_from_database(self, reset_database, test_status) -> None: - """Tests to see if the statuses's data can be returned from database. - - Resets the database. Creates statuses table. Builds and saves - test_status. Calls test_status.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status = test_status - test_status.save(db) - - data = test_status.read(db)[0] - expected = [-19, "ACTIVE", "Active"] - - assert ( - data[0] == expected[0] and data[1] == expected[1] and data[2] == expected[2] - ) - - def test_can_load_status_from_database(self, reset_database, test_status) -> None: - """Tests to see if an Unit Object can be loaded from data. - Loads and saves test_status. Creates loaded_unit from data. - - Asserts that test_status and loaded_unit return identical - attributes. - """ - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status = test_status - test_status.save(db) - - loaded_unit = db.load_status("ACTIVE") - - assert ( - loaded_unit.return_attributes()[0] == test_status.return_attributes()[0] - and loaded_unit.return_attributes()[1] == test_status.return_attributes()[1] - and loaded_unit.return_attributes()[2] == test_status.return_attributes()[2] - ) - - def test_can_update_status_in_database(self, reset_database, test_status) -> None: - """Tests to see if Unit data can be updated. - - Resets database. Creates Unit Table. Builds and saves test_status to - database. Loads test_status as loaded_unit. Changes Name and updates - database. Queries the data. - - Asserts that the returned data has the new name. - """ - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status = test_status - test_status.save(db) - - test_status.status_name = "Not Tina" - - test_status.update(db) - - data = db.return_data( - """SELECT status_name FROM statuses WHERE status_code = 'ACTIVE'""" - )[0][0] - - assert data == "Not Tina" - - def test_can_delete_status_from_database(self, test_status, reset_database): - """Tests that statuses can be deleted from the database. - - Loads test_status. Saves it to database. Then deletes it. Gets data from - statuses table. - - Asserts data is empty. - """ - test_status = test_status - - with database.Database("test_database.db") as db: - db.create_table(statuses.return_table_creation_query()) - - test_status.save(db) - test_status.delete(db) - - data = db.return_data("""SELECT * FROM statuses""") - assert data == [] - - def test_return_attributes(self, test_status): - """Tests that the statuses data is correctly returned. - - Loads test_status. Calls test_status.return_attributes(). - - Asserts values returned are expected values. - """ - test_status = test_status - assert test_status.return_attributes() == ( - -19, - "ACTIVE", - "Active", - "Used for items which are currently in use.", - database.return_datetime("2022-08-01 00:00:00"), - database.return_datetime("2022-08-01 00:00:00"), - "Abenthy", - ) diff --git a/tests/unit/units_test.py b/tests/unit/units_test.py deleted file mode 100644 index 5a6ff73..0000000 --- a/tests/unit/units_test.py +++ /dev/null @@ -1,367 +0,0 @@ -"""Contains the classes and tests which test the units module. - -Classes: - - Test_UnitsModule: Contains all unit tests for the units module. - - Test_UnitAttributes: Contains unit tests for Unit's attributes. - - Test_UnitsMethods: Contains unit tests for the Unit's methods. -""" - -from narcotics_tracker import database, units - - -class Test_UnitsModule: - """Contains all unit tests for the Units module. - - Behaviors Tested: - - Units module can be accessed. - - Method return_table_creation_query returns correct string. - - Method return_units returns all units. - - Method parse_unit_data returns correct dictionary and values. - """ - - def test_units_module_can_be_accessed(self) -> None: - """Tests that the units module exists and can be accessed. - - Asserts that calling units.__doc__ does not return 'None'. - """ - assert units.__doc__ != None - - def test_return_table_creation_query_returns_expected_string(self) -> None: - """Tests that the table_creation_query returns the correct string. - - Calls units.return_table_creation_query(). - - Asserts that expected_query is returned. - """ - expected_query = """CREATE TABLE IF NOT EXISTS units ( - UNIT_ID INTEGER PRIMARY KEY, - UNIT_CODE TEXT UNIQUE, - UNIT_NAME TEXT, - CREATED_DATE INTEGER, - MODIFIED_DATE INTEGER, - MODIFIED_BY TEXT - )""" - - assert units.return_table_creation_query() == expected_query - - def test_return_units_returns_expected_units( - self, test_unit, reset_database - ) -> None: - """Tests that the return_units method returns the expected units. - - Loads and saves test_unit. Creates and save. 2nd_unit - Calls units.return_units(). - - Asserts that units.return_units() returns expected data. - """ - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit = test_unit - - test_unit.save(db) - - units_list = units.return_units(db) - - assert "Unit Number 821: Tina. Code: 'tn'." in units_list[0] - - def test_parse_unit_data_returns_correct_values( - self, reset_database, test_unit - ) -> None: - """Tests if parse_unit_data returns dictionary with correct data. - - Resets the database. Creates units table. Builds and saves - test_unit to database. Queries database for unit data and - calls the parser. - - Asserts that dictionary returned assigns the correct data to correct - keys. - """ - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit = test_unit - test_unit.save(db) - data = test_unit.read(db) - dictionary = units.parse_unit_data(data) - - assert ( - dictionary["unit_id"] == 821 - and dictionary["unit_code"] == "tn" - and dictionary["unit_name"] == "Tina" - ) - - -class Test_UnitAttributes: - """Contains all unit tests for the Unit Class' attributes. - - Behaviors Tested: - - Unit class can be accessed. - - Unit objects can be created. - - unit_id attribute returns correct value. - - unit_code attribute returns correct value. - - unit_name attribute returns correct value. - - created_date attribute returns correct value. - - modified_date attribute returns correct value. - - modified_by attribute returns correct value. - """ - - def test_unit_class_can_be_accessed(self) -> None: - """Tests that the Unit Class exists and can be accessed. - - Asserts that calling Unit.__doc__ does not return 'None'. - """ - assert units.Unit.__doc__ != None - - def test_can_create_unit_objects(self, test_unit) -> None: - """Tests that objects can be created from the Unit Class. - - Loads test_unit. - - Asserts that test_unit is an instance of the Unit Class. - """ - test_unit = test_unit - - assert isinstance(test_unit, units.Unit) - - def test_unit_id_returns_correct_value(self, test_unit) -> None: - """Tests that the unit_id attribute returns the correct value. - - Loads test_unit. - - Asserts test_unit.unit_id is '821'. - """ - test_unit = test_unit - - assert test_unit.unit_id == 821 - - def test_unit_code_returns_correct_value(self, test_unit) -> None: - """Tests that the unit_code attribute returns the correct value. - - Loads test_unit. - - Asserts that test_unit.unit_code is 'tn'. - """ - test_unit = test_unit - - assert test_unit.unit_code == "tn" - - def test_unit_name_returns_correct_value(self, test_unit) -> None: - """Tests that the unit_name attributes returns the correct value. - - Loads test_unit. - - Asserts that test_unit.unit_name is 'Tina' - """ - test_unit = test_unit - - assert test_unit.unit_name == "Tina" - - def test_created_date_returns_correct_value(self, test_unit) -> None: - """Tests that the created_date attributes returns the correct value. - - Loads test_unit. - - Asserts that test_unit.created_date is '08-26-2022' - """ - test_unit = test_unit - - assert test_unit.created_date == database.return_datetime("2022-08-01 00:00:00") - - def test_modified_date_returns_correct_value(self, test_unit) -> None: - """Tests that the modified_date attributes returns the correct value. - - Loads test_unit. - - Asserts that test_unit.modified_date is '08-01-2022' - """ - test_unit = test_unit - - assert test_unit.modified_date == database.return_datetime( - "2022-08-01 00:00:00" - ) - - def test_modified_by_returns_correct_value(self, test_unit) -> None: - """Tests that the modified_by attributes returns the correct value. - - Loads test_unit. - - Asserts that test_unit.modified_by is 'Denna' - """ - test_unit = test_unit - - assert test_unit.modified_by == "Denna" - - -class Test_UnitMethods: - """Contains all unit tests for the Unit Class' methods. - - Behaviors Tested: - - __init__ sets attributes correctly. - - __repr__ returns correct string. - - Can save Unit to database. - - Can read Unit data from database. - - Can load Unit from database. - - Can update Unit in database. - - Can delete Unit from database. - - return_attributes returns the correct values. - """ - - def test___init___sets_attributes_correctly(self, test_unit) -> None: - """Tests that the initializer sets the objects attributes correctly. - - Loads test_unit. - - Asserts that unit_id, unit_code, and unit_name attributes are set to - the expected values. - """ - test_unit = test_unit - - assert ( - test_unit.unit_id == 821 - and test_unit.unit_code == "tn" - and test_unit.unit_name == "Tina" - ) - - def test___repr___returns_expected_string(self, test_unit) -> None: - """Tests that __repr__ returns correct string. - - Loads test_unit. Calls str(test_unit). - - Asserts that str(test_unit) returns: - 'Unit Test. Code: TEST. Used for testing the EventType Class.' - """ - test_unit = test_unit - expected = f"Unit Number 821: Tina. Code: 'tn'." - - assert str(test_unit) == expected - - def test_can_save_unit_to_database(self, test_unit, reset_database) -> None: - """Tests that Units can be saved to the database. - - Loads test_unit. Calls test_unit.save. Calls db.return_data() - using the unit_id of '821'. - - Asserts that returned data has unit_code value of 'tn'. - """ - test_unit = test_unit - - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit.save(db) - - data = db.return_data( - """SELECT unit_code FROM units WHERE unit_id = '821'""" - ) - - assert data[0][0] == "tn" - - def test_can_read_unit_from_database(self, reset_database, test_unit) -> None: - """Tests to see if the units's data can be returned from database. - - Resets the database. Creates units table. Builds and saves - test_unit. Calls test_unit.read(). - - Asserts that data returned matches expected values. - """ - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit = test_unit - test_unit.save(db) - - data = test_unit.read(db)[0] - expected = [821, "tn", "Tina"] - - assert ( - data[0] == expected[0] and data[1] == expected[1] and data[2] == expected[2] - ) - - def test_can_load_unit_from_database(self, reset_database, test_unit) -> None: - """Tests to see if a Unit Object can be loaded from data. - - Loads and saves test_unit. Creates loaded_unit from data. - - Asserts that test_unit and loaded_unit return identical - attributes. - """ - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit = test_unit - test_unit.save(db) - - loaded_unit = db.load_unit("tn") - - assert ( - loaded_unit.return_attributes()[0] == test_unit.return_attributes()[0] - and loaded_unit.return_attributes()[1] == test_unit.return_attributes()[1] - and loaded_unit.return_attributes()[2] == test_unit.return_attributes()[2] - ) - - def test_can_update_unit_in_database(self, reset_database, test_unit) -> None: - """Tests to see if Unit data can be updated. - - Resets database. Creates Unit Table. Builds and saves test_unit to - database. Loads test_unit as loaded_unit. Changes Name and updates - database. Queries the data. - - Asserts that the returned data has the new name. - """ - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit = test_unit - test_unit.save(db) - - test_unit.unit_name = "Not Tina" - - test_unit.update(db) - - data = db.return_data( - """SELECT unit_name FROM units WHERE unit_code = 'tn'""" - )[0][0] - - assert data == "Not Tina" - - def test_can_delete_unit_from_database(self, test_unit, reset_database) -> None: - """Tests that Units can be deleted from the database. - - Loads test_unit. Saves it to database. Then deletes it. Gets data from - units table. - - Asserts data is empty. - """ - test_unit = test_unit - - with database.Database("test_database.db") as db: - db.create_table(units.return_table_creation_query()) - - test_unit.save(db) - test_unit.delete(db) - - data = db.return_data("""SELECT * FROM units""") - assert data == [] - - def test_return_attributes(self, test_unit) -> None: - """Tests that the Units data is correctly returned. - - Loads test_unit. Calls test_unit.return_attributes(). - - Asserts values returned are expected values. - """ - test_unit = test_unit - expected = ( - 821, - "tn", - "Tina", - database.return_datetime("2022-08-01 00:00:00"), - database.return_datetime("2022-08-01 00:00:00"), - "Denna", - ) - - assert test_unit.return_attributes() == expected diff --git a/tests/unit/utilities_test.py b/tests/unit/utilities_test.py deleted file mode 100644 index 4204964..0000000 --- a/tests/unit/utilities_test.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Contains the classes used to unit tests the utils package. - -Classes: - Test_UnitConverter: Contains all unit tests for the unit_converter module. - Test_Utilities: Contains all unit tests for the utilities module. - -""" -from narcotics_tracker.utils import unit_converter - - -class Test_UnitConverter: - """Contains all unit tests for the unit_converter module. - - Behaviors Tested: - - Can convert mg to mcg. - - Can convert mg to G. - - Can convert G to mcg. - - Can convert G to mg. - - Can convert mcg to mg. - - Can convert mcg to G. - """ - - def test_convert_mg_to_mcg(self) -> None: - """Check to see if mg can be converted to mcg. - - Asserts that 5 mg returns 5_000 mcg. - """ - assert unit_converter.UnitConverter.to_mcg(5, "mg") == 5_000 - - def test_convert_G_to_mcg(self) -> None: - """Check to see if G can be converted to mcg. - - Asserts that 0.9 G returns 900_000 mcg. - """ - assert unit_converter.UnitConverter.to_mcg(0.9, "g") == 900_000 - - def test_convert_mcg_to_mg(self) -> None: - """Check to see if mcg can be converted to mg. - - Asserts that 500 mcg returns 0.5 mg. - """ - assert unit_converter.UnitConverter.to_mg(500, "mcg") == 0.5 - - def test_convert_G_to_mg(self) -> None: - """Check to see if G can be converted to mg. - - Asserts that 0.9 G returns 900 mg. - """ - assert unit_converter.UnitConverter.to_mg(0.9, "g") == 900 - - def test_convert_mcg_to_G(self) -> None: - """Check to see if mcg can be converted to G. - - Asserts that 1 mcg returns 0.000001 G. - """ - assert unit_converter.UnitConverter.to_G(1, "mcg") == 0.000001 - - def test_convert_mg_to_G(self) -> None: - """ - Test that mg is converted to G. - """ - assert unit_converter.UnitConverter.to_G(5, "mg") == 0.005