Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/JReyna217/PharmaVault/llms.txt

Use this file to discover all available pages before exploring further.

The Inventory page is the core workspace of PharmaVault. It lets you maintain a personal list of the medications you currently own — recording quantities, purchase dates, expiration dates, and any prescription notes for each item. Every entry is tied to a record in the medicine catalog so that drug metadata (name, form, dosage) is managed centrally rather than duplicated across your stock.

The InventoryItemDto Model

When the inventory list is fetched from the database, each row is mapped to an InventoryItemDto. This read-optimised projection joins the inventory and medicine_catalog tables so the UI receives everything it needs in a single round-trip.
namespace PharmaVault.Core.Models;

public class InventoryItemDto
{
    public int InventoryId { get; set; }
    public int CatalogId { get; set; }

    public string MedicineName { get; set; } = string.Empty;
    public string PharmaceuticalForm { get; set; } = string.Empty;
    public string? Dosage { get; set; }

    public int Quantity { get; set; }
    public DateTime? PurchaseDate { get; set; }
    public DateTime ExpirationDate { get; set; }
    public string? PrescriptionNotes { get; set; }
    public DateTime DateAdded { get; set; }

    // Computed — not stored in the database
    public bool IsExpired => ExpirationDate < DateTime.Today;
}
InventoryId
int
required
Primary key of the inventory row. Generated by PostgreSQL on insert (RETURNING inventory_id).
CatalogId
int
required
Foreign key to medicine_catalog.catalog_id. Links this stock entry to the catalog record that provides MedicineName, PharmaceuticalForm, and Dosage.

The Inventory Model

The write-side model used for insert and update operations carries validation attributes that are enforced by Blazor’s EditForm before any database call is made.
using System.ComponentModel.DataAnnotations;

namespace PharmaVault.Core.Models;

public class Inventory
{
    public int InventoryId { get; set; }

    public int UserId { get; set; }

    [Range(1, int.MaxValue, ErrorMessage = "Please select a medicine from the catalog.")]
    public int CatalogId { get; set; }

    [Range(1, 10000, ErrorMessage = "Quantity must be at least 1.")]
    public int Quantity { get; set; }

    public DateTime? PurchaseDate { get; set; }

    [Required(ErrorMessage = "The expiration date is required.")]
    public DateTime ExpirationDate { get; set; }

    [StringLength(500, ErrorMessage = "Notes cannot exceed 500 characters.")]
    public string? PrescriptionNotes { get; set; }

    public DateTime DateAdded { get; set; }
}
CatalogId must be a valid catalog_id from the medicine catalog. The default value of 0 fails the [Range(1, int.MaxValue)] check, so the form cannot be submitted without selecting a medicine from the catalog dropdown.

CRUD Operations

Returns all inventory rows for a given user, ordered by expiration_date ASC so the items closest to expiry appear at the top of the list.Interface signature:
Task<IEnumerable<InventoryItemDto>> GetUserInventoryAsync(int userId);
The underlying SQL performs an INNER JOIN with medicine_catalog to populate the medicine name, pharmaceutical form, and dosage on each InventoryItemDto. The ::timestamp casts ensure that PostgreSQL date columns are mapped correctly to C# DateTime properties.Called from: Inventory.razor.cs → LoadInventoryAsync() during OnInitializedAsync and after every write operation.
Inserts a new row into the inventory table and returns the newly generated primary key.Interface signature:
Task<int> AddToInventoryAsync(Inventory inventory);
The UserId is always taken from the authenticated session — it is never accepted from user-supplied form input. The new inventory_id is returned via PostgreSQL’s RETURNING clause.Returns: The int primary key (inventory_id) of the inserted row.
Updates mutable fields on an existing inventory row and returns true if one row was affected.Interface signature:
Task<bool> UpdateInventoryAsync(Inventory inventory);
The UPDATE statement targets inventory_id AND user_id, so a user can only modify their own rows. The following fields can be changed on edit:
FieldNotes
QuantityNew unit count (1–10,000)
PurchaseDateMay be set to null
ExpirationDateRequired; cannot be cleared
PrescriptionNotesMay be set to null or up to 500 chars
CatalogId (the linked medicine) cannot be changed after an inventory item is created. To associate a different medicine, delete the existing entry and add a new one.
Returns: true if the row was found and updated; false if no matching row existed (e.g., wrong userId).
Removes a single inventory row permanently.Interface signature:
Task<bool> DeleteFromInventoryAsync(int inventoryId, int userId);
The WHERE clause requires both inventory_id = @InventoryId AND user_id = @UserId. This dual-condition check prevents one authenticated user from deleting another user’s inventory rows, even if they know the target inventoryId.Returns: true if the row was deleted; false if no matching row was found.
Aggregates a user’s inventory quantities into four buckets used by the dashboard counters and pie chart.Interface signature:
Task<DashboardStatsDto> GetDashboardStatsAsync(int userId);
The underlying PostgreSQL query uses CASE expressions evaluated against CURRENT_DATE to distribute each row’s quantity into the correct bucket — expired, expiring soon, or good — in a single round-trip. COALESCE(..., 0) ensures users with no inventory rows receive zeroes rather than NULL.Returns: A populated DashboardStatsDto with TotalStock, ExpiredStock, ExpiringSoonStock, and GoodStock counters. Returns a default (all-zero) instance if no rows exist.Called from: Dashboard.razor.cs → OnInitializedAsync().
The inventory list supports instant, client-side filtering via the FilteredInventory computed property. No additional database round-trip is made — filtering runs over the in-memory _inventoryItems collection that was loaded on page initialisation.
protected IEnumerable<InventoryItemDto> FilteredInventory
{
    get
    {
        if (_inventoryItems == null)
            return Enumerable.Empty<InventoryItemDto>();

        if (string.IsNullOrWhiteSpace(_searchTerm))
            return _inventoryItems;

        return _inventoryItems.Where(i =>
            i.MedicineName.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase) ||
            (i.PharmaceuticalForm != null &&
             i.PharmaceuticalForm.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase))
        );
    }
}
Filtering is case-insensitive and matches against two fields simultaneously:
  • MedicineName — the drug name from the catalog (e.g., searching "para" matches Paracetamol).
  • PharmaceuticalForm — the dosage form (e.g., searching "tablet" filters to all tablet entries).
The search term is bound two-way to _searchTerm, so the list updates on every keystroke without requiring a button press. Every inventory item must reference a valid catalog_id from the medicine catalog. The inventory form populates its medicine dropdown by calling IMedicineCatalogDao.GetAllAsync(), which returns all catalog records ordered by name. Only medicines with IsActive = true will be surfaced in the dropdown — inactive catalog entries cannot be added to new inventory rows.
If the medicine you want to stock does not appear in the dropdown, navigate to the Medicine Catalog page and add it there first before returning to create the inventory entry.

Build docs developers (and LLMs) love