Documentation
¶
Overview ¶
Package inventory provides a complete inventory management layer for use with SQLite databases. It has with full CRUD support, with CSV/JSON import/export, and ready-to-use CLI and web features.
बी.वी.एल - बोसजी के द्वारा रचित भंडार लेखांकन हेतु तन्त्राक्ष्।
=============================================
एक सुगम एवं उपयोगी भंडार संचालन हेतु तन्त्राक्ष्।
एक रचनात्मक भारतीय उत्पाद ।
bvl - Boseji's Inventory Management Program
Package inventory ¶
Core Features:
- InventoryDB wrapper: safe, transactional DB access - In-memory / file SQLite support - Configurable sequence start (IndexStart)
Data Model:
- Item struct: ID, Description, Location, Status, Remarks (with FormatRemarks)
- FormatRemarks(): consistent timestamped remarks
- JSON tags for web/app/API compatibility
Database Operations:
- AddItem() - EditItem() - DeleteItem() - AppendItem() - AppendRemarksEntry() - GetItemByID() - ListAll() - ListItemsPaged() with pagination - NewItemIterator() with streaming Next() - ResetSequence()
CSV Support:
- ExportCSV() - ImportCSV() - ViewCSV() - InventoryDB wrappers - CLI-friendly and Excel-friendly format
JSON Support:
- ExportJSON() - ImportJSON() - ViewJSON() - ExportJSONToString() - ImportJSONFromString() - InventoryDB wrappers - CLI-friendly, Web API-ready format - jq / Web / Electron compatibility
Item JSON helpers:
- Item.ToJSON() - Item.FromJSON()
Test Suite:
- db_test.go: core DB functions - inventorydb_test.go: InventoryDB methods - csv_test.go: CSV - json_test.go: JSON - All error paths covered - Rollback scenarios covered
Ready for:
- CLI tools - Web frontends - Electron apps - REST APIs - jq pipelines - Automation scripts - CI/CD integration
License:
This package is GPL-2.0-only.
bvl - Boseji's Inventory Management Program. Copyright (C) 2025 by Abhijit Bose (aka. Boseji).
SPDX-License-Identifier: GPL-2.0-only Full Name: GNU General Public License v2.0 only Please visit <https://spdx.org/licenses/GPL-2.0-only.html> for details.
Sources: https://github.com/boseji/bvl
Index ¶
- Constants
- func AddItem(exec Execer, item Item) error
- func AppendItem(exec Execer, item Item) error
- func AppendRemarksEntry(exec Execer, id int, message string) error
- func DeleteItem(exec Execer, id int) error
- func EditItem(exec Execer, item Item) error
- func ExportCSV(db *sql.DB, filename string) error
- func ExportJSON(db *sql.DB, filename string) error
- func ExportJSONToString(db *sql.DB) (string, error)
- func ImportCSV(exec Execer, filename string) error
- func ImportJSON(exec Execer, filename string) error
- func ImportJSONFromBytes(exec Execer, data []byte) error
- func ImportJSONFromString(exec Execer, jsonString string) error
- func OpenDB(dbFile string) *sql.DB
- func ResetSequence(exec Execer) error
- func ViewCSV(filename string) error
- func ViewJSON(filename string) error
- type Execer
- type InventoryDB
- func (inv *InventoryDB) AddItem(item Item) error
- func (inv *InventoryDB) AppendItem(item Item) error
- func (inv *InventoryDB) AppendRemarksEntry(id int, message string) error
- func (inv *InventoryDB) Close() error
- func (inv *InventoryDB) DB() *sql.DB
- func (inv *InventoryDB) DeleteItem(id int) error
- func (inv *InventoryDB) EditItem(item Item) error
- func (inv *InventoryDB) ExportCSV(filename string) error
- func (inv *InventoryDB) ExportJSON(filename string) error
- func (inv *InventoryDB) ExportJSONToString() (string, error)
- func (inv *InventoryDB) GetItemByID(id int) (Item, error)
- func (inv *InventoryDB) ImportCSV(filename string) error
- func (inv *InventoryDB) ImportJSON(filename string) error
- func (inv *InventoryDB) ImportJSONFromString(jsonString string) error
- func (inv *InventoryDB) ListAll() ([]Item, error)
- func (inv *InventoryDB) ListItemsPaged(afterID int, limit int) ([]Item, error)
- func (inv *InventoryDB) NewItemIterator(whereClause string, args ...interface{}) (*ItemIterator, error)
- func (inv *InventoryDB) ResetSequence() error
- func (inv *InventoryDB) WithTransaction(fn func(tx Execer) error) error
- type Item
- type ItemIterator
Constants ¶
const (
// Starting value of the Index
IndexStart = 1000
)
IndexStart defines the starting value for auto-incremented IDs.
Variables ¶
This section is empty.
Functions ¶
func AddItem ¶
AddItem inserts a new item into the inventory table.
The ID is assigned automatically (auto-increment). The remarks field is always stored in timestamped format by calling item.FormatRemarks().
Usage:
item := Item{
Description: "New inverter",
Location: "Warehouse 1",
Status: "Operational",
Remarks: "installed and tested",
}
err := AddItem(tx, item)
Resulting remarks field:
[2025-06-20 16:45] installed and tested
Notes: - If used inside a transaction, pass tx as exec - If using plain db connection, pass db as exec - Remarks will always follow consistent format - Works with both *sql.DB and *sql.Tx.
func AppendItem ¶
AppendItem inserts or replaces an item in the inventory table, using the provided ID. If an item with the same ID already exists, it will be replaced with the new values.
The remarks field is processed through item.FormatRemarks() to ensure consistent timestamped formatting.
Typical usage:
item := Item{
ID: 1234,
Description: "UPS 3KVA",
Location: "Rack 5",
Status: "Operational",
Remarks: "installed new unit",
}
err := AppendItem(tx, item)
Resulting record:
id = 1234 description = "UPS 3KVA" location = "Rack 5" status = "Operational" remarks = "[2025-06-20 12:30] installed new unit"
Use cases:
- To update an existing record fully (replace) - To insert a new record with known ID - To bulk insert/update items
Notes:
- Safe to call repeatedly with the same item - Will replace existing record (INSERT OR REPLACE) - Does not check for ID conflicts beyond replacement - Remarks field will always be formatted via FormatRemarks() - If ID is not set, use AddItem() instead - Works with both *sql.DB and *sql.Tx.
func AppendRemarksEntry ¶
AppendRemarksEntry appends a new log entry to the item's remarks field, using the standard timestamp format.
The entry is formatted as:
[YYYY-MM-DD HH:MM] message
This function uses SQL to append without loading the entire remarks field:
SET remarks = COALESCE(remarks, '') || char(10) || ?
Usage:
err := AppendRemarksEntry(tx, 1002, "replaced battery")
Resulting remarks field:
(previous remarks) [2025-06-20 16:55] replaced battery
Notes: - Does not modify other fields (description, location, status) - If item ID does not exist, no rows are updated - Use when you only want to add an audit/log entry - Works with both *sql.DB and *sql.Tx.
func DeleteItem ¶
DeleteItem removes a record from the inventory table by ID.
If the specified ID does not exist, the operation is a no-op (no error is returned).
Typical usage:
err := inv.DeleteItem(1234)
Result:
- If item with id = 1234 exists → record is deleted - If no such item → nothing is done, no error
Use cases:
- To permanently remove an inventory record - To clean up old or duplicate items - To reset part of the inventory manually
Notes:
- This is a destructive operation (cannot be undone) - Should typically be logged via remarks before use - Use AppendRemarksEntry() if you want an audit trail before delete - Works with both *sql.DB and *sql.Tx.
func EditItem ¶
EditItem updates the item's fields (description, location, status) and appends the new remarks text to the existing remarks field.
Remarks field acts as an append-only log:
- Previous remarks are preserved
- New entry is appended with timestamp format: [YYYY-MM-DD HH:MM] message
This function does not load existing remarks in Go; it performs the append using SQL:
SET remarks = COALESCE(remarks, '') || char(10) || ?
Usage:
item := Item{
ID: 1002,
Description: "Updated inverter",
Location: "Warehouse 3",
Status: "Operational",
Remarks: "maintenance check completed",
}
err := EditItem(tx, item)
Resulting remarks field:
(previous remarks) [2025-06-20 16:22] maintenance check completed
Notes: - If item ID does not exist, no rows are updated - If used inside transaction (tx), pass tx as exec - To append a single new log entry, use AppendRemarksEntry() - To display remarks nicely, use item.FormatRemarks() - Works with both *sql.DB and *sql.Tx.
func ExportCSV ¶
ExportCSV writes all inventory records to a CSV file.
Usage:
err := ExportCSV(db, "inventory.csv")
The CSV will have the following columns:
id, description, location, status, remarks
Existing file will be overwritten.
Returns error if file cannot be written or query fails.
func ExportJSON ¶
ExportJSON writes all inventory records to a JSON file.
The output JSON is an array of Item objects:
[
{ "id": 1001, "description": "UPS", "location": "Rack 1", ... },
{ ... },
...
]
Usage:
err := ExportJSON(db, "inventory.json")
Example:
// Export inventory to file err := inventory.ExportJSON(db, "export.json")
Errors:
- returns error if database query fails
- returns error if JSON marshal fails
- returns error if file cannot be written (permission, path)
func ExportJSONToString ¶
ExportJSONToString returns all inventory records as a JSON string.
Usage:
jsonStr, err := ExportJSONToString(db)
Example:
jsonStr, err := inventory.ExportJSONToString(db)
Useful for:
- Web API response
- Electron UI
- jq processing
- CLI --json flag
func ImportCSV ¶
ImportCSV reads inventory records from a CSV file and imports them.
Existing records with matching IDs will be replaced.
Usage:
err := ImportCSV(db, "inventory.csv")
CSV format must have columns:
id, description, location, status, remarks
Each row is imported using AppendItem().
Returns error on file error, parse error, or DB error.
func ImportJSON ¶
ImportJSON reads inventory records from a JSON file and imports them.
Existing records with matching IDs will be replaced.
Usage:
err := ImportJSON(exec, "inventory.json")
Example:
// Import from JSON err := inventory.ImportJSON(inv, "import.json")
Errors:
- returns error if file read fails
- returns error if JSON unmarshal fails
- returns error if individual Insert/Replace fails
func ImportJSONFromBytes ¶
ImportJSONFromBytes helper
func ImportJSONFromString ¶
ImportJSONFromString reads inventory records from a JSON string.
Existing records with matching IDs will be replaced.
Usage:
err := ImportJSONFromString(exec, jsonString)
Example:
err := inventory.ImportJSONFromString(inv, jsonPayload)
Errors:
- returns error if JSON is invalid
- returns error if DB insert fails
func OpenDB ¶
OpenDB opens or creates the SQLite database file at dbFile path.
It ensures that the 'inventory' table exists with the required fields: - id INTEGER PRIMARY KEY AUTOINCREMENT - description TEXT - location TEXT - status TEXT - remarks TEXT
It also ensures that the autoincrement sequence is initialized: - If the sequence is missing, sets it to IndexStart.
Usage:
db := OpenDB("inventory.db")
Notes: - Returns a *sql.DB connection (ready to use) - Fails fatally if the database cannot be opened or schema is invalid - Table creation is idempotent (safe to call multiple times) - Auto-increment starts from IndexStart (default 1000)
func ResetSequence ¶
ResetSequence wraps ResetSequence with automatic transaction.
Resets the auto-increment sequence for the inventory table back to IndexStart (default: 1000).
Typically used after manually clearing records, or for test setups.
Usage:
err := inv.ResetSequence()
Result:
- Sets the internal sqlite_sequence counter for 'inventory' table - Next inserted record will use ID = IndexStart + 1
Use cases:
- After deleting all items (clear inventory) - For test environments to reset IDs - To reinitialize an empty database
Notes:
- Does not delete records (use DeleteItem or manual purge first) - Safe to call multiple times - Has no effect if records still exist with higher IDs - Works with both *sql.DB and *sql.Tx.
func ViewCSV ¶
ViewCSV prints the content of a CSV file to stdout.
Usage:
err := ViewCSV("inventory.csv")
The output is formatted as columns:
id description location status remarks
Errors are returned if the file cannot be read.
Types ¶
type InventoryDB ¶
type InventoryDB struct {
// contains filtered or unexported fields
}
InventoryDB wraps *sql.DB and provides safe transaction helpers.
Users do not need to work with *sql.DB directly.
func NewInventoryDB ¶
func NewInventoryDB(dbFile string) *InventoryDB
NewInventoryDB opens or creates the database and returns InventoryDB.
Ensures the table exists, sequence is initialized. Returns a ready-to-use InventoryDB wrapper.
Usage:
inv := NewInventoryDB("inventory.db")
Notes: - Underlying connection is stored in inv.db - Close() must be called when finished - Table creation is idempotent
func (*InventoryDB) AddItem ¶
func (inv *InventoryDB) AddItem(item Item) error
AddItem wraps AddItem with automatic transaction.
Usage:
err := inv.AddItem(item)
func (*InventoryDB) AppendItem ¶
func (inv *InventoryDB) AppendItem(item Item) error
AppendItem wraps AppendItem with automatic transaction.
Usage:
err := inv.AppendItem(item)
func (*InventoryDB) AppendRemarksEntry ¶
func (inv *InventoryDB) AppendRemarksEntry(id int, message string) error
AppendRemarksEntry wraps AppendRemarksEntry with automatic transaction.
Usage:
err := inv.AppendRemarksEntry(id, "log message")
func (*InventoryDB) Close ¶
func (inv *InventoryDB) Close() error
Close closes the underlying database connection.
func (*InventoryDB) DB ¶
func (inv *InventoryDB) DB() *sql.DB
DB returns the underlying *sql.DB (for read-only queries). Use only when needed, e.g. for GetItemByID.
func (*InventoryDB) DeleteItem ¶
func (inv *InventoryDB) DeleteItem(id int) error
DeleteItem wraps DeleteItem with automatic transaction.
Usage:
err := inv.DeleteItem(id)
func (*InventoryDB) EditItem ¶
func (inv *InventoryDB) EditItem(item Item) error
EditItem wraps EditItem with automatic transaction.
Usage:
err := inv.EditItem(item)
func (*InventoryDB) ExportCSV ¶
func (inv *InventoryDB) ExportCSV(filename string) error
ExportCSV writes all inventory records to CSV using InventoryDB.
Usage:
err := inv.ExportCSV("inventory.csv")
Same as ExportCSV() raw.
func (*InventoryDB) ExportJSON ¶
func (inv *InventoryDB) ExportJSON(filename string) error
InventoryDB method: ExportJSON
Usage:
err := inv.ExportJSON("inventory.json")
func (*InventoryDB) ExportJSONToString ¶
func (inv *InventoryDB) ExportJSONToString() (string, error)
InventoryDB method: ExportJSONToString
Usage:
jsonStr, err := inv.ExportJSONToString()
func (*InventoryDB) GetItemByID ¶
func (inv *InventoryDB) GetItemByID(id int) (Item, error)
GetItemByID wraps GetItemByID.
Usage:
item, err := inv.GetItemByID(id)
func (*InventoryDB) ImportCSV ¶
func (inv *InventoryDB) ImportCSV(filename string) error
ImportCSV imports inventory records from CSV using InventoryDB.
Usage:
err := inv.ImportCSV("inventory.csv")
The import runs inside a transaction.
func (*InventoryDB) ImportJSON ¶
func (inv *InventoryDB) ImportJSON(filename string) error
InventoryDB method: ImportJSON
Usage:
err := inv.ImportJSON("inventory.json")
Runs inside transaction.
func (*InventoryDB) ImportJSONFromString ¶
func (inv *InventoryDB) ImportJSONFromString(jsonString string) error
InventoryDB method: ImportJSONFromString
Usage:
err := inv.ImportJSONFromString(jsonString)
Runs inside transaction.
func (*InventoryDB) ListAll ¶
func (inv *InventoryDB) ListAll() ([]Item, error)
ListAll wraps ListAll.
Usage:
items, err := inv.ListAll()
func (*InventoryDB) ListItemsPaged ¶
func (inv *InventoryDB) ListItemsPaged(afterID int, limit int) ([]Item, error)
ListItemsPaged wraps ListItemsPaged.
Usage:
items, err := inv.ListItemsPaged(afterID, limit)
func (*InventoryDB) NewItemIterator ¶
func (inv *InventoryDB) NewItemIterator( whereClause string, args ...interface{}, ) (*ItemIterator, error)
NewItemIterator returns an ItemIterator for scanning records with an optional WHERE clause.
Usage:
iter, err := inv.NewItemIterator("WHERE status = ?", "Operational")
if err != nil {
// handle error
}
defer iter.Close()
for {
item, ok, err := iter.Next()
if err != nil {
// handle error
}
if !ok {
break // end of results
}
fmt.Println(item.ID, item.Description)
}
func (*InventoryDB) ResetSequence ¶
func (inv *InventoryDB) ResetSequence() error
ResetSequence wraps ResetSequence with automatic transaction.
Usage:
err := inv.ResetSequence()
func (*InventoryDB) WithTransaction ¶
func (inv *InventoryDB) WithTransaction( fn func(tx Execer) error) error
WithTransaction executes the given function inside a transaction.
Usage:
err := inv.WithTransaction(func(tx Execer) error {
err := AddItem(tx, item)
if err != nil {
return err
}
return AppendRemarksEntry(tx, item.ID, "added new")
})
If fn() returns error: - Transaction is rolled back
If fn() returns nil: - Transaction is committed
Notes: - Use for any group of changes that must be atomic - If the DB fails, returns error
type Item ¶
type Item struct {
ID int `json:"id"`
Description string `json:"description"`
Location string `json:"location"`
Status string `json:"status"`
Remarks string `json:"remarks"`
}
Item represents an inventory record.
Fields:
ID - auto-increment primary key Description - free text Location - free text Status - free text Remarks - audit log, may contain timestamped entries
The Remarks field is typically maintained using FormatRemarks() to ensure consistent timestamp format.
Example:
[2025-06-21 14:30] installed new battery
The Item struct is used across all DB, CSV, and JSON functions.
func GetItemByID ¶
GetItemByID returns a single item from the inventory table that matches the given ID.
If no item is found with the given ID, returns an error:
"item <id> not found"
Typical usage:
item, err := inv.GetItemByID(1234)
if err != nil {
// handle error (not found, or query error)
} else {
fmt.Println(item.Description, item.Status)
}
Result:
- If item exists → returns populated Item struct - If not found → returns zero-value Item + error
Use cases:
- To display or edit a specific inventory item - To retrieve details for audit or reporting - To check existence of an item by ID
Notes:
- This is a read-only query (no transaction needed)
- The remarks field is returned as raw string (use item.FormatRemarks() for formatted display)
func ListAll ¶
ListAll returns all items in the inventory table, sorted by ID.
This is a read-only operation. It does not require a transaction. It can be used for reporting, exporting, or displaying all items.
Usage:
items, err := inv.ListAll()
if err != nil {
// handle error
}
for _, item := range items {
fmt.Println(item.ID, item.Description, item.Status)
}
Result:
- Returns []Item containing all inventory records - Sorted by id ASC (oldest first)
Use cases:
- To display the full inventory - To export data to CSV, JSON - For reports or dashboards
Notes:
- If the table is empty, returns an empty slice (no error)
- The remarks field will be returned in raw form (use item.FormatRemarks() for display)
- This method does not paginate large inventories (use ListItemsPaged for that)
- Use cautiously for very large databases. For pagination, use ListItemsPaged() or ItemIterator().
func ListItemsPaged ¶
ListItemsPaged returns a slice of items after a given starting ID, up to a specified limit.
This is a read-only operation. It does not require a transaction.
Usage:
items, err := inv.ListItemsPaged(lastID, 10)
if err != nil {
// handle error
}
for _, item := range items {
fmt.Printf("%d: %s\n", item.ID, item.Description)
}
Result:
- Returns up to 'limit' number of items with id > afterID - Results are sorted by id ASC
Use cases:
- For paging through large inventories - For implementing UI pagination - For batch export or processing
Notes:
- If no items match the query, returns an empty slice - Use afterID = 0 to start from beginning - If fewer than 'limit' items remain, returns as many as available
func (*Item) FormatRemarks ¶
FormatRemarks returns the Remarks field formatted as:
[YYYY-MM-DD HH:MM] <remarks>
If Remarks is already formatted (starts with timestamp), it returns Remarks unchanged.
If Remarks is blank, returns only timestamp prefix.
Usage:
formatted := item.FormatRemarks()
This function is used by AddItem(), AppendItem(), EditItem() to ensure Remarks field is consistent.
Example output:
"[2025-06-21 15:00] installed UPS"
func (*Item) FromJSON ¶
FromJSON parses a JSON string into this Item.
Usage:
var item Item err := item.FromJSON(jsonStr)
Example:
var item Item
err := item.FromJSON(`{"id":1001,"description":"UPS"}`)
Errors:
- returns error if JSON is invalid
func (*Item) ToJSON ¶
ToJSON returns this Item as a JSON string.
Usage:
jsonStr, err := item.ToJSON()
Example output:
{
"id": 1001,
"description": "UPS",
"location": "Rack 1",
"status": "Operational",
"remarks": "[2025-06-21 15:00] installed UPS"
}
Useful for:
- Logging
- CLI --json
- Websocket events
- API single-item
type ItemIterator ¶
type ItemIterator struct {
// contains filtered or unexported fields
}
ItemIterator provides a streaming interface to iterate over inventory records, one item at a time.
Internally uses sql.Rows and rows.Next(). Allows for processing large result sets with low memory usage.
Usage:
iter, err := NewItemIterator(db, "WHERE status = ?", "Operational")
if err != nil {
// handle error
}
defer iter.Close()
for {
item, ok, err := iter.Next()
if err != nil {
// handle error
}
if !ok {
break // end of results
}
fmt.Println(item.ID, item.Description)
}
Use cases:
- To process large inventories without loading all into memory - To stream records to external systems - To filter results with WHERE clause
Notes:
- You must call Close() when done to release database resources - The iterator must be used in a single goroutine - If WHERE clause is empty (""), all records are returned - Always check for error on Next() even if ok == false
func NewItemIterator ¶
func NewItemIterator( db *sql.DB, whereClause string, args ...interface{}, ) (*ItemIterator, error)
NewItemIterator returns an ItemIterator for scanning records in the inventory table with an optional WHERE clause.
The iterator streams results one at a time and uses minimal memory.
Usage:
iter, err := NewItemIterator(inv.DB(), "WHERE status = ?", "Operational")
if err != nil {
// handle error
}
defer iter.Close()
for {
item, ok, err := iter.Next()
if err != nil {
// handle error
}
if !ok {
break // end of results
}
fmt.Println(item.ID, item.Description)
}
Use cases:
- To process large inventories without loading entire table - To filter items with a dynamic WHERE clause - To support streaming export to CSV, JSON, etc.
Notes:
- WHERE clause must begin with "WHERE ..." or be empty string "" - Use parameter substitution for arguments (? placeholders) - Must call Close() when done to release database resources
func (*ItemIterator) Close ¶
func (it *ItemIterator) Close() error
Close releases the database resources held by this iterator.
You must call Close() when you are finished iterating, otherwise database connections may be leaked.
Usage:
iter, err := NewItemIterator(inv.DB(), "WHERE status = ?", "Operational")
if err != nil {
// handle error
}
defer iter.Close() // always defer Close!
for {
item, ok, err := iter.Next()
if err != nil {
// handle error
}
if !ok {
break
}
fmt.Println(item.ID, item.Description)
}
Use cases:
- Always used after calling NewItemIterator() - Should be deferred immediately after iterator creation
Notes:
- Safe to call even if no rows were read - Safe to call multiple times (subsequent calls will do nothing) - Does not affect the underlying database connection
func (*ItemIterator) Next ¶
func (it *ItemIterator) Next() (Item, bool, error)
Next returns the next item from the iterator.
Usage:
for {
item, ok, err := iter.Next()
if err != nil {
// handle error (scan error)
}
if !ok {
break // no more rows
}
fmt.Println(item.ID, item.Description)
}
Return values:
- item: the next Item in the result set (if ok == true) - ok: true if a row was returned, false if at end of result set - err: non-nil if scan failed
Use cases:
- To process items one at a time from a filtered query - For streaming export (CSV, JSON) - For large inventories without loading all items into memory
Notes:
- Must check err even if ok == false - Must call Close() on the iterator after use - Each call advances the cursor (forward-only) - This is not thread-safe: use only in single goroutine