Skip to main content

Overview

The Units of Measure catalog manages measurement units used throughout the system for quantifying materials, products, and dimensions.

Data Model

The UnitOfMeasure model is defined in app/models/unit_of_measure.py:6:
class UnitOfMeasure(db.Model):
    __tablename__ = 'unit_of_measures'
    
    id_unit_of_measure = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False, unique=True)
    abbreviation = db.Column(db.String(10), nullable=False, unique=True)
    active = db.Column(db.Boolean, nullable=False, default=True)
    
    created_at = db.Column(db.TIMESTAMP, server_default=func.current_timestamp())
    updated_at = db.Column(db.TIMESTAMP, server_onupdate=func.current_timestamp())
    deleted_at = db.Column(db.TIMESTAMP, nullable=True)
    
    created_by = db.Column(db.String(100), nullable=True)
    updated_by = db.Column(db.String(100), nullable=True)
    deleted_by = db.Column(db.String(100), nullable=True)

Fields

FieldTypeDescription
id_unit_of_measureIntegerPrimary key
nameString(50)Full unit name (e.g., “Meters”)
abbreviationString(10)Short form (e.g., “m”)
activeBooleanSoft delete flag
created_atTimestampRecord creation date
updated_atTimestampLast modification date
deleted_atTimestampLogical deletion date
Both name and abbreviation must be unique across all units.

Service Layer

The UnitOfMeasureService class in app/catalogs/unit_of_measures/services.py:13 provides business logic:

Get All Units

Retrieves all active units of measure:
units = UnitOfMeasureService.get_all()
Implementation from services.py:17:
@staticmethod
def get_all() -> list[UnitOfMeasure]:
    return UnitOfMeasure.query.filter_by(active=True).all()

Create Unit of Measure

Creates a new unit with name and abbreviation:
data = {
    "name": "Meters",
    "abbreviation": "m",
    "active": True
}
unit = UnitOfMeasureService.create(data)
Implementation from services.py:27:
@staticmethod
def create(data: dict) -> dict:
    name = data.get("name", "").strip()
    abbreviation = data.get("abbreviation", "").strip()
    active = data.get("active", True)
    
    if not name:
        raise ValidationError("El nombre de la unidad de medida es requerido")
    if not abbreviation:
        raise ValidationError("La abreviatura de la unidad de medida es requerida")
    
    existing = UnitOfMeasure.query.filter(
        func.lower(UnitOfMeasure.name) == func.lower(name)
    ).first()
    if existing:
        raise ConflictError(f"Ya existe una unidad de medida con el nombre '{name}'")
    
    unit_of_measure = UnitOfMeasure(
        name=name,
        abbreviation=abbreviation,
        active=active
    )
    db.session.add(unit_of_measure)
    db.session.commit()
    
    return unit_of_measure.to_dict()
The service performs case-insensitive name validation using func.lower() to ensure uniqueness.

Get Unit by ID

Retrieves a specific unit of measure:
unit = UnitOfMeasureService.get_by_id(id_unit_of_measure)
Implementation from services.py:66:
@staticmethod
def get_by_id(id_unit_of_measure: int) -> UnitOfMeasure:
    unit_of_measure = UnitOfMeasure.query.get(id_unit_of_measure)
    if not unit_of_measure:
        raise NotFoundError("Unidad de medida no encontrada")
    return unit_of_measure

Update Unit of Measure

Updates an existing unit:
data = {
    "name": "Square Meters",
    "abbreviation": "m²"
}
UnitOfMeasureService.update(id_unit_of_measure, data)
Implementation from services.py:85:
@staticmethod
def update(id_unit_of_measure: int, data: dict) -> dict:
    unit_of_measure = UnitOfMeasureService.get_by_id(id_unit_of_measure)
    
    name = data.get("name", "").strip()
    abbreviation = data.get("abbreviation", "").strip()
    
    if not name:
        raise ValidationError("El nombre de la unidad de medida es requerido")
    
    if not abbreviation:
        raise ValidationError("La abreviatura de la unidad de medida es requerida")
    
    existing = UnitOfMeasure.query.filter(
        func.lower(UnitOfMeasure.name) == func.lower(name),
        UnitOfMeasure.id_unit_of_measure != id_unit_of_measure
    ).first()
    
    if existing:
        raise ConflictError(f"Ya existe una unidad de medida con el nombre '{name}'")
    
    unit_of_measure.name = name
    unit_of_measure.abbreviation = abbreviation
    db.session.commit()
    
    return unit_of_measure.to_dict()

Delete Unit of Measure

Performs a soft delete:
UnitOfMeasureService.delete(id_unit_of_measure)
Implementation from services.py:134:
@staticmethod
def delete(id_unit_of_measure: int) -> None:
    unit_of_measure = UnitOfMeasureService.get_by_id(id_unit_of_measure)
    unit_of_measure.active = False
    unit_of_measure.deleted_at = func.current_timestamp()
    db.session.commit()

Routes

The routes module (app/catalogs/unit_of_measures/routes.py) defines HTTP endpoints:

List Units of Measure

Endpoint: GET /unit-of-measures/ From routes.py:18:
@unit_of_measures_bp.route("/", methods=["GET"])
def list_unit_of_measures():
    unit_of_measures = UnitOfMeasureService.get_all()
    return render_template("unit_of_measures/list.html", unit_of_measures=unit_of_measures)

Create Unit of Measure

Endpoint: GET/POST /unit-of-measures/create From routes.py:29:
@unit_of_measures_bp.route("/create", methods=["GET", "POST"])
def create_unit_of_measure():
    form = UnitOfMeasureForm()
    
    if form.validate_on_submit():
        data = {
            "name": form.name.data,
            "abbreviation": form.abbreviation.data,
            "active": form.active.data
        }
        try:
            UnitOfMeasureService.create(data)
            flash("Unidad de medida creada exitosamente", "success")
            return redirect(url_for("unit_of_measures.list_unit_of_measures"))
        except (ConflictError, ValidationError) as e:
            flash(str(e), "danger")
    
    return render_template("unit_of_measures/create.html", form=form)

Edit Unit of Measure

Endpoint: GET/POST /unit-of-measures/<id_unit_of_measure>/edit From routes.py:59:
@unit_of_measures_bp.route("/<int:id_unit_of_measure>/edit", methods=["GET", "POST"])
def edit_unit_of_measure(id_unit_of_measure):
    try:
        unit_of_measure = UnitOfMeasureService.get_by_id(id_unit_of_measure)
    except NotFoundError:
        flash("Unidad de medida no encontrada", "danger")
        return redirect(url_for("unit_of_measures.list_unit_of_measures"))
    
    form = UnitOfMeasureForm()
    
    if form.validate_on_submit():
        data = {
            "name": form.name.data,
            "abbreviation": form.abbreviation.data
        }
        try:
            UnitOfMeasureService.update(id_unit_of_measure, data)
            flash("Unidad de medida actualizada exitosamente", "success")
            return redirect(url_for("unit_of_measures.list_unit_of_measures"))
        except (ConflictError, ValidationError) as e:
            flash(str(e), "danger")
    
    elif request.method == "GET":
        form.name.data = unit_of_measure.name
        form.abbreviation.data = unit_of_measure.abbreviation
        form.active.data = unit_of_measure.active
    
    return render_template("unit_of_measures/edit.html", form=form, unit_of_measure=unit_of_measure)

Delete Unit of Measure

Endpoint: POST /unit-of-measures/<id_unit_of_measure>/delete From routes.py:101:
@unit_of_measures_bp.route("/<int:id_unit_of_measure>/delete", methods=["POST"])
def delete_unit_of_measure(id_unit_of_measure: int):
    try:
        UnitOfMeasureService.delete(id_unit_of_measure)
        flash("Unidad de medida eliminada exitosamente", "success")
    except NotFoundError:
        flash("Unidad de medida no encontrada", "danger")
    
    return redirect(url_for("unit_of_measures.list_unit_of_measures"))

Form Definition

The UnitOfMeasureForm class in app/catalogs/unit_of_measures/forms.py:10 defines the input form:
class UnitOfMeasureForm(FlaskForm):
    name = StringField(
        "Nombre",
        validators=[
            DataRequired(message="El nombre de la unidad de medida es requerido"),
            Length(max=50, message="El nombre no puede exceder 50 caracteres"),
        ],
    )
    abbreviation = StringField(
        "Abreviatura",
        validators=[
            DataRequired(message="La abreviatura de la unidad de medida es requerida"),
            Length(max=10, message="La abreviatura no puede exceder 10 caracteres"),
        ]
    )
    active = BooleanField("Activo", default=True)

Validators

FieldValidators
nameDataRequired, Length(max=50)
abbreviationDataRequired, Length(max=10)
activeNone (defaults to True)

Usage Examples

Common Units of Measure

Typical units in a furniture manufacturing system:

Length

  • Meters (m)
  • Centimeters (cm)
  • Millimeters (mm)
  • Inches (in)
  • Feet (ft)

Area

  • Square Meters (m²)
  • Square Feet (ft²)

Volume

  • Cubic Meters (m³)
  • Liters (L)
  • Gallons (gal)

Weight

  • Kilograms (kg)
  • Grams (g)
  • Pounds (lb)

Quantity

  • Units (u)
  • Pieces (pcs)
  • Boxes (box)

Creating Standard Units

from app.catalogs.unit_of_measures.services import UnitOfMeasureService

# Define standard units
standard_units = [
    {"name": "Meters", "abbreviation": "m"},
    {"name": "Centimeters", "abbreviation": "cm"},
    {"name": "Square Meters", "abbreviation": "m²"},
    {"name": "Kilograms", "abbreviation": "kg"},
    {"name": "Units", "abbreviation": "u"},
    {"name": "Pieces", "abbreviation": "pcs"},
]

for unit_data in standard_units:
    try:
        result = UnitOfMeasureService.create(unit_data)
        print(f"Created: {result['name']} ({result['abbreviation']})")
    except ConflictError as e:
        print(f"Already exists: {unit_data['name']}")

Querying Units

# Get all active units
units = UnitOfMeasureService.get_all()
for unit in units:
    print(f"{unit.name} ({unit.abbreviation})")

# Get specific unit
unit = UnitOfMeasureService.get_by_id(1)
print(f"Unit: {unit.name} - {unit.abbreviation}")

Best Practices

1

Use Standard Abbreviations

Follow international standards (SI units, common industry abbreviations)
2

Be Consistent

Use either metric or imperial units consistently within measurement types
3

Avoid Redundancy

Don’t create multiple units for the same measurement (e.g., both “m” and “meter”)
4

Plan for Conversions

Consider future unit conversion requirements when defining units
  • Use standard abbreviations: “m” not “mts”, “kg” not “kgs”
  • Keep abbreviations short (max 10 characters)
  • Use lowercase for most units: “m”, “kg”, “cm”
  • Use uppercase for specific units: “L” (liters), “C” (Celsius)
  • Use special characters when needed: “m²”, “m³”, “°C”
Choose one system as primary based on your region:Metric (International):
  • Meters, centimeters, millimeters
  • Kilograms, grams
  • Square meters, cubic meters
Imperial (US):
  • Feet, inches
  • Pounds, ounces
  • Square feet, cubic feet

Integration Examples

Material Quantities

# Example: Track wood inventory by unit
material = {
    "name": "Oak Plywood",
    "quantity": 50,
    "unit_id": 3,  # Square Meters
}

Product Dimensions

# Example: Define furniture dimensions
product = {
    "name": "Dining Table",
    "length": 200,
    "width": 100,
    "height": 75,
    "dimension_unit_id": 2,  # Centimeters
}
Units of measure are foundational for inventory tracking and production planning.

Build docs developers (and LLMs) love