diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json
index 7dfceed..23e3f97 100644
--- a/client/public/locales/en/common.json
+++ b/client/public/locales/en/common.json
@@ -134,6 +134,7 @@
"id": "ID",
"filament_name": "Filament",
"filament": "Filament",
+ "price": "Price",
"material": "Material",
"weight_to_use": "Weight",
"used_weight": "Used Weight",
@@ -150,6 +151,7 @@
"archived": "Archived"
},
"fields_help": {
+ "price": "Price of a full spool in the system configured currency. If not set, the price of the filament will be assumed instead.",
"weight_to_use": "Select what weight value to enter. Measured weight is only available if the spool weight is set for the selected filament.",
"used_weight": "How much filament has been used from the spool. A new spool should have 0g used.",
"remaining_weight": "How much filament is left on the spool. For a new spool this should match the spool weight.",
diff --git a/client/src/pages/spools/create.tsx b/client/src/pages/spools/create.tsx
index f9c41f7..d33d6ff 100644
--- a/client/src/pages/spools/create.tsx
+++ b/client/src/pages/spools/create.tsx
@@ -201,7 +201,20 @@ export const SpoolCreate: React.FC
-
+
+
+
diff --git a/client/src/pages/spools/edit.tsx b/client/src/pages/spools/edit.tsx
index 1bbce4b..86b84ce 100644
--- a/client/src/pages/spools/edit.tsx
+++ b/client/src/pages/spools/edit.tsx
@@ -181,6 +181,20 @@ export const SpoolEdit: React.FC = () => {
}}
/>
+
+
+
diff --git a/client/src/pages/spools/list.tsx b/client/src/pages/spools/list.tsx
index cc17f38..714a6f8 100644
--- a/client/src/pages/spools/list.tsx
+++ b/client/src/pages/spools/list.tsx
@@ -52,6 +52,9 @@ function collapseSpool(element: ISpool): ISpoolCollapsed {
} else {
filament_name = element.filament.name ?? element.filament.id.toString();
}
+ if (!element.price){
+ element.price = element.filament.price;
+ }
return {
...element,
combined_name: filament_name,
@@ -73,6 +76,7 @@ const allColumns: (keyof ISpoolCollapsed & string)[] = [
"id",
"combined_name",
"filament.material",
+ "price",
"used_weight",
"remaining_weight",
"used_length",
@@ -312,6 +316,14 @@ export const SpoolList: React.FC = () => {
filterValueQuery: useSpoolmanMaterials(),
width: 120,
}),
+ SortedColumn({
+ id: "price",
+ i18ncat: "spool",
+ actions,
+ dataSource,
+ tableState,
+ width: 80,
+ }),
NumberColumn({
id: "used_weight",
i18ncat: "spool",
diff --git a/client/src/pages/spools/model.tsx b/client/src/pages/spools/model.tsx
index 7dd64d2..f365d03 100644
--- a/client/src/pages/spools/model.tsx
+++ b/client/src/pages/spools/model.tsx
@@ -6,6 +6,7 @@ export interface ISpool {
first_used?: string;
last_used?: string;
filament: IFilament;
+ price?: number;
remaining_weight?: number;
used_weight: number;
remaining_length?: number;
diff --git a/client/src/pages/spools/show.tsx b/client/src/pages/spools/show.tsx
index 2765310..f068b00 100644
--- a/client/src/pages/spools/show.tsx
+++ b/client/src/pages/spools/show.tsx
@@ -23,6 +23,15 @@ export const SpoolShow: React.FC = () => {
const record = data?.data;
+ const spoolPrice = (item: ISpool) => {
+ let spoolPrice = "";
+ if (!item.price){
+ spoolPrice = `${item.filament.price}`;
+ return spoolPrice;
+ }
+ return item.price;
+ }
+
const formatFilament = (item: IFilament) => {
let vendorPrefix = "";
if (item.vendor) {
@@ -58,6 +67,8 @@ export const SpoolShow: React.FC = () => {
{t("spool.fields.filament")}
+ {t("spool.fields.price")}
+
{t("spool.fields.registered")}
None:
+ """Perform the upgrade."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column("spool", sa.Column("price", sa.Float(), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ """Perform the downgrade."""
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("spool", "price")
+ # ### end Alembic commands ###
diff --git a/spoolman/api/v1/models.py b/spoolman/api/v1/models.py
index b8a77f1..0be5a14 100644
--- a/spoolman/api/v1/models.py
+++ b/spoolman/api/v1/models.py
@@ -133,6 +133,11 @@ class Spool(BaseModel):
first_used: Optional[datetime] = Field(description="First logged occurence of spool usage. UTC Timezone.")
last_used: Optional[datetime] = Field(description="Last logged occurence of spool usage. UTC Timezone.")
filament: Filament = Field(description="The filament type of this spool.")
+ price: Optional[float] = Field(
+ ge=0,
+ description="The price of this spool in the system configured currency.",
+ example=20.0,
+ )
remaining_weight: Optional[float] = Field(
default=None,
ge=0,
@@ -197,6 +202,7 @@ class Spool(BaseModel):
first_used=item.first_used,
last_used=item.last_used,
filament=filament,
+ price=item.price,
used_weight=item.used_weight,
used_length=used_length,
remaining_weight=remaining_weight,
diff --git a/spoolman/api/v1/spool.py b/spoolman/api/v1/spool.py
index f9d083c..35ec5ea 100644
--- a/spoolman/api/v1/spool.py
+++ b/spoolman/api/v1/spool.py
@@ -34,6 +34,11 @@ class SpoolParameters(BaseModel):
first_used: Optional[datetime] = Field(description="First logged occurence of spool usage.")
last_used: Optional[datetime] = Field(description="Last logged occurence of spool usage.")
filament_id: int = Field(description="The ID of the filament type of this spool.")
+ price: Optional[float] = Field(
+ ge=0,
+ description="The price of this filament in the system configured currency.",
+ example=20.0,
+ )
remaining_weight: Optional[float] = Field(
ge=0,
description=(
@@ -336,6 +341,7 @@ async def create( # noqa: ANN201
db_item = await spool.create(
db=db,
filament_id=body.filament_id,
+ price=body.price,
remaining_weight=body.remaining_weight,
used_weight=body.used_weight,
first_used=body.first_used,
diff --git a/spoolman/database/models.py b/spoolman/database/models.py
index 05e4f4c..45df367 100644
--- a/spoolman/database/models.py
+++ b/spoolman/database/models.py
@@ -50,6 +50,7 @@ class Spool(Base):
registered: Mapped[datetime] = mapped_column()
first_used: Mapped[Optional[datetime]] = mapped_column()
last_used: Mapped[Optional[datetime]] = mapped_column()
+ price: Mapped[Optional[float]] = mapped_column()
filament_id: Mapped[int] = mapped_column(ForeignKey("filament.id"))
filament: Mapped["Filament"] = relationship(back_populates="spools")
used_weight: Mapped[float] = mapped_column()
diff --git a/spoolman/database/spool.py b/spoolman/database/spool.py
index 98336b7..dc29d32 100644
--- a/spoolman/database/spool.py
+++ b/spoolman/database/spool.py
@@ -38,6 +38,7 @@ async def create(
used_weight: Optional[float] = None,
first_used: Optional[datetime] = None,
last_used: Optional[datetime] = None,
+ price: Optional[float] = None,
location: Optional[str] = None,
lot_nr: Optional[str] = None,
comment: Optional[str] = None,
@@ -63,6 +64,7 @@ async def create(
filament=filament_item,
registered=datetime.utcnow().replace(microsecond=0),
used_weight=used_weight,
+ price=price,
first_used=first_used,
last_used=last_used,
location=location,
diff --git a/tests_integration/tests/spool/test_add.py b/tests_integration/tests/spool/test_add.py
index f5ae7fd..fa64f48 100644
--- a/tests_integration/tests/spool/test_add.py
+++ b/tests_integration/tests/spool/test_add.py
@@ -19,6 +19,7 @@ def test_add_spool_remaining_weight(random_filament: dict[str, Any]):
lot_nr = "123456789"
comment = "abcdefghåäö"
archived = True
+ price = 25
result = httpx.post(
f"{URL}/api/v1/spool",
json={
@@ -30,6 +31,7 @@ def test_add_spool_remaining_weight(random_filament: dict[str, Any]):
"lot_nr": lot_nr,
"comment": comment,
"archived": archived,
+ "price": price,
},
)
result.raise_for_status()
@@ -62,6 +64,7 @@ def test_add_spool_remaining_weight(random_filament: dict[str, Any]):
"lot_nr": lot_nr,
"comment": comment,
"archived": archived,
+ "price": price,
}
# Verify that registered happened almost now (within 1 minute)
diff --git a/tests_integration/tests/spool/test_get.py b/tests_integration/tests/spool/test_get.py
index acb8345..fa4e0f4 100644
--- a/tests_integration/tests/spool/test_get.py
+++ b/tests_integration/tests/spool/test_get.py
@@ -16,6 +16,7 @@ def test_get_spool(random_filament: dict[str, Any]):
location = "The Pantry"
lot_nr = "123456789"
comment = "abcdefghåäö"
+ price = 25
archived = True
result = httpx.post(
f"{URL}/api/v1/spool",
@@ -27,6 +28,7 @@ def test_get_spool(random_filament: dict[str, Any]):
"location": location,
"lot_nr": lot_nr,
"comment": comment,
+ "price": price,
"archived": archived,
},
)
diff --git a/tests_integration/tests/spool/test_update.py b/tests_integration/tests/spool/test_update.py
index c1d3be3..feaad44 100644
--- a/tests_integration/tests/spool/test_update.py
+++ b/tests_integration/tests/spool/test_update.py
@@ -33,6 +33,7 @@ def test_update_spool(random_filament: dict[str, Any]):
lot_nr = "987654321"
comment = "abcdefghåäö"
archived = True
+ price = 25
result = httpx.patch(
f"{URL}/api/v1/spool/{spool['id']}",
json={
@@ -43,6 +44,7 @@ def test_update_spool(random_filament: dict[str, Any]):
"lot_nr": lot_nr,
"comment": comment,
"archived": archived,
+ "price": price,
},
)
result.raise_for_status()
@@ -71,6 +73,7 @@ def test_update_spool(random_filament: dict[str, Any]):
assert spool["lot_nr"] == lot_nr
assert spool["comment"] == comment
assert spool["archived"] == archived
+ assert spool["price"] == price
# Clean up
httpx.delete(f"{URL}/api/v1/spool/{spool['id']}").raise_for_status()