Support for damaged parts

This commit is contained in:
Gregoo 2025-01-31 20:46:36 +01:00
parent 2ee8fdb058
commit 47358d0a54
37 changed files with 274 additions and 91 deletions

View File

@ -107,9 +107,10 @@
# Default: false # Default: false
# BK_HIDE_ALL_SETS=true # BK_HIDE_ALL_SETS=true
# Optional: Hide the 'Missing' entry from the menu. Does not disable the route. # Optional: Hide the 'Problems' entry from the menu. Does not disable the route.
# Default: false # Default: false
# BK_HIDE_MISSING_PARTS=true # Legacy name: BK_HIDE_MISSING_PARTS
# BK_HIDE_PROBLEMS_PARTS=true
# Optional: Hide the 'Instructions' entry in a Set card # Optional: Hide the 'Instructions' entry in a Set card
# Default: false # Default: false

View File

@ -29,7 +29,7 @@ CONFIG: Final[list[dict[str, Any]]] = [
{'n': 'HIDE_ALL_MINIFIGURES', 'c': bool}, {'n': 'HIDE_ALL_MINIFIGURES', 'c': bool},
{'n': 'HIDE_ALL_PARTS', 'c': bool}, {'n': 'HIDE_ALL_PARTS', 'c': bool},
{'n': 'HIDE_ALL_SETS', 'c': bool}, {'n': 'HIDE_ALL_SETS', 'c': bool},
{'n': 'HIDE_MISSING_PARTS', 'c': bool}, {'n': 'HIDE_PROBLEMS_PARTS', 'e': 'BK_HIDE_MISSING_PARTS', 'c': bool},
{'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool}, {'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool},
{'n': 'HIDE_WISHES', 'c': bool}, {'n': 'HIDE_WISHES', 'c': bool},
{'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"rebrickable_minifigures"."name" ASC'}, # noqa: E501 {'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"rebrickable_minifigures"."name" ASC'}, # noqa: E501

View File

@ -21,10 +21,11 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
# Queries # Queries
all_query: str = 'minifigure/list/all' all_query: str = 'minifigure/list/all'
damaged_part_query: str = 'minifigure/list/damaged_part'
last_query: str = 'minifigure/list/last' last_query: str = 'minifigure/list/last'
missing_part_query: str = 'minifigure/list/missing_part'
select_query: str = 'minifigure/list/from_set' select_query: str = 'minifigure/list/from_set'
using_part_query: str = 'minifigure/list/using_part' using_part_query: str = 'minifigure/list/using_part'
missing_part_query: str = 'minifigure/list/missing_part'
def __init__(self, /): def __init__(self, /):
super().__init__() super().__init__()
@ -47,6 +48,23 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self return self
# Minifigures with a part damaged part
def damaged_part(self, part: str, color: int, /) -> Self:
# Save the parameters to the fields
self.fields.part = part
self.fields.color = color
# Load the minifigures from the database
for record in self.select(
override_query=self.damaged_part_query,
order=self.order
):
minifigure = BrickMinifigure(record=record)
self.records.append(minifigure)
return self
# Last added minifigure # Last added minifigure
def last(self, /, *, limit: int = 6) -> Self: def last(self, /, *, limit: int = 6) -> Self:
# Randomize # Randomize
@ -80,12 +98,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self return self
# Minifigures missing a part # Minifigures missing a part
def missing_part( def missing_part(self, part: str, color: int, /) -> Self:
self,
part: str,
color: int,
/,
) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.fields.part = part self.fields.part = part
self.fields.color = color self.fields.color = color
@ -102,12 +115,7 @@ class BrickMinifigureList(BrickRecordList[BrickMinifigure]):
return self return self
# Minifigure using a part # Minifigure using a part
def using_part( def using_part(self, part: str, color: int, /) -> Self:
self,
part: str,
color: int,
/,
) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.fields.part = part self.fields.part = part
self.fields.color = color self.fields.color = color

View File

@ -11,7 +11,7 @@ NAVBAR: Final[list[dict[str, Any]]] = [
{'e': 'set.list', 't': 'Sets', 'i': 'grid-line', 'f': 'HIDE_ALL_SETS'}, # noqa: E501 {'e': 'set.list', 't': 'Sets', 'i': 'grid-line', 'f': 'HIDE_ALL_SETS'}, # noqa: E501
{'e': 'add.add', 't': 'Add', 'i': 'add-circle-line', 'f': 'HIDE_ADD_SET'}, # noqa: E501 {'e': 'add.add', 't': 'Add', 'i': 'add-circle-line', 'f': 'HIDE_ADD_SET'}, # noqa: E501
{'e': 'part.list', 't': 'Parts', 'i': 'shapes-line', 'f': 'HIDE_ALL_PARTS'}, # noqa: E501 {'e': 'part.list', 't': 'Parts', 'i': 'shapes-line', 'f': 'HIDE_ALL_PARTS'}, # noqa: E501
{'e': 'part.missing', 't': 'Missing', 'i': 'error-warning-line', 'f': 'HIDE_MISSING_PARTS'}, # noqa: E501 {'e': 'part.problem', 't': 'Problems', 'i': 'error-warning-line', 'f': 'HIDE_PROBLEMS_PARTS'}, # noqa: E501
{'e': 'minifigure.list', 't': 'Minifigures', 'i': 'group-line', 'f': 'HIDE_ALL_MINIFIGURES'}, # noqa: E501 {'e': 'minifigure.list', 't': 'Minifigures', 'i': 'group-line', 'f': 'HIDE_ALL_MINIFIGURES'}, # noqa: E501
{'e': 'instructions.list', 't': 'Instructions', 'i': 'file-line', 'f': 'HIDE_ALL_INSTRUCTIONS'}, # noqa: E501 {'e': 'instructions.list', 't': 'Instructions', 'i': 'file-line', 'f': 'HIDE_ALL_INSTRUCTIONS'}, # noqa: E501
{'e': 'wish.list', 't': 'Wishlist', 'i': 'gift-line', 'f': 'HIDE_WISHES'}, {'e': 'wish.list', 't': 'Wishlist', 'i': 'gift-line', 'f': 'HIDE_WISHES'},

View File

@ -74,9 +74,12 @@ class BrickPart(RebrickablePart):
return True return True
# A identifier for HTML component # A identifier for HTML component
def html_id(self, /) -> str: def html_id(self, prefix: str | None = None, /) -> str:
components: list[str] = ['part'] components: list[str] = ['part']
if prefix is not None:
components.append(prefix)
if self.fields.figure is not None: if self.fields.figure is not None:
components.append(self.fields.figure) components.append(self.fields.figure)
@ -144,36 +147,38 @@ class BrickPart(RebrickablePart):
return self return self
# Update the missing part # Update a problematic part
def update_missing(self, json: Any | None, /) -> None: def update_problem(self, problem: str, json: Any | None, /) -> int:
missing: str | int = json.get('value', '') # type: ignore amount: str | int = json.get('value', '') # type: ignore
# We need a positive integer # We need a positive integer
try: try:
if missing == '': if amount == '':
missing = 0 amount = 0
missing = int(missing) amount = int(amount)
if missing < 0: if amount < 0:
missing = 0 amount = 0
except Exception: except Exception:
raise ErrorException('"{missing}" is not a valid integer'.format( raise ErrorException('"{amount}" is not a valid integer'.format(
missing=missing amount=amount
)) ))
if missing < 0: if amount < 0:
raise ErrorException('Cannot set a negative missing value') raise ErrorException('Cannot set a negative amount')
self.fields.missing = missing setattr(self.fields, problem, amount)
BrickSQL().execute_and_commit( BrickSQL().execute_and_commit(
'part/update/missing', 'part/update/{problem}'.format(problem=problem),
parameters=self.sql_parameters() parameters=self.sql_parameters()
) )
# Compute the url for missing part return amount
def url_for_missing(self, /) -> str:
# Compute the url for problematic part
def url_for_problem(self, problem: str, /) -> str:
# Different URL for a minifigure part # Different URL for a minifigure part
if self.minifigure is not None: if self.minifigure is not None:
figure = self.minifigure.fields.figure figure = self.minifigure.fields.figure
@ -181,10 +186,11 @@ class BrickPart(RebrickablePart):
figure = None figure = None
return url_for( return url_for(
'set.missing_part', 'set.problem_part',
id=self.fields.id, id=self.fields.id,
figure=figure, figure=figure,
part=self.fields.part, part=self.fields.part,
color=self.fields.color, color=self.fields.color,
spare=self.fields.spare, spare=self.fields.spare,
problem=problem,
) )

View File

@ -25,7 +25,7 @@ class BrickPartList(BrickRecordList[BrickPart]):
all_query: str = 'part/list/all' all_query: str = 'part/list/all'
last_query: str = 'part/list/last' last_query: str = 'part/list/last'
minifigure_query: str = 'part/list/from_minifigure' minifigure_query: str = 'part/list/from_minifigure'
missing_query: str = 'part/list/missing' problem_query: str = 'part/list/problem'
print_query: str = 'part/list/from_print' print_query: str = 'part/list/from_print'
select_query: str = 'part/list/specific' select_query: str = 'part/list/specific'
@ -138,10 +138,10 @@ class BrickPartList(BrickRecordList[BrickPart]):
return self return self
# Load missing parts # Load problematic parts
def missing(self, /) -> Self: def problem(self, /) -> Self:
for record in self.select( for record in self.select(
override_query=self.missing_query, override_query=self.problem_query,
order=self.order order=self.order
): ):
part = BrickPart(record=record) part = BrickPart(record=record)

View File

@ -18,6 +18,8 @@ class BrickSetList(BrickRecordList[BrickSet]):
order: str order: str
# Queries # Queries
damaged_minifigure_query: str = 'set/list/damaged_minifigure'
damaged_part_query: str = 'set/list/damaged_part'
generic_query: str = 'set/list/generic' generic_query: str = 'set/list/generic'
light_query: str = 'set/list/light' light_query: str = 'set/list/light'
missing_minifigure_query: str = 'set/list/missing_minifigure' missing_minifigure_query: str = 'set/list/missing_minifigure'
@ -57,6 +59,39 @@ class BrickSetList(BrickRecordList[BrickSet]):
return self return self
# Sets with a minifigure part damaged
def damaged_minifigure(self, figure: str, /) -> Self:
# Save the parameters to the fields
self.fields.figure = figure
# Load the sets from the database
for record in self.select(
override_query=self.damaged_minifigure_query,
order=self.order
):
brickset = BrickSet(record=record)
self.records.append(brickset)
return self
# Sets with a part damaged
def damaged_part(self, part: str, color: int, /) -> Self:
# Save the parameters to the fields
self.fields.part = part
self.fields.color = color
# Load the sets from the database
for record in self.select(
override_query=self.damaged_part_query,
order=self.order
):
brickset = BrickSet(record=record)
self.records.append(brickset)
return self
# A generic list of the different sets # A generic list of the different sets
def generic(self, /) -> Self: def generic(self, /) -> Self:
for record in self.select( for record in self.select(
@ -90,7 +125,7 @@ class BrickSetList(BrickRecordList[BrickSet]):
return self return self
# Sets missing a minifigure # Sets missing a minifigure part
def missing_minifigure(self, figure: str, /) -> Self: def missing_minifigure(self, figure: str, /) -> Self:
# Save the parameters to the fields # Save the parameters to the fields
self.fields.figure = figure self.fields.figure = figure

View File

@ -7,6 +7,9 @@ SELECT
{% block total_missing %} {% block total_missing %}
NULL AS "total_missing", -- dummy for order: total_missing NULL AS "total_missing", -- dummy for order: total_missing
{% endblock %} {% endblock %}
{% block total_damaged %}
NULL AS "total_damaged", -- dummy for order: total_damaged
{% endblock %}
{% block total_quantity %} {% block total_quantity %}
NULL AS "total_quantity", -- dummy for order: total_quantity NULL AS "total_quantity", -- dummy for order: total_quantity
{% endblock %} {% endblock %}

View File

@ -1,7 +1,11 @@
{% extends 'minifigure/base/base.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_missing %} {% block total_missing %}
SUM(IFNULL("missing_join"."total", 0)) AS "total_missing", SUM(IFNULL("problem_join"."total_missing", 0)) AS "total_missing",
{% endblock %}
{% block total_damaged %}
SUM(IFNULL("problem_join"."total_damaged", 0)) AS "total_damaged",
{% endblock %} {% endblock %}
{% block total_quantity %} {% block total_quantity %}
@ -18,15 +22,16 @@ LEFT JOIN (
SELECT SELECT
"bricktracker_parts"."id", "bricktracker_parts"."id",
"bricktracker_parts"."figure", "bricktracker_parts"."figure",
SUM("bricktracker_parts"."missing") AS total SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("bricktracker_parts"."damaged") AS "total_damaged"
FROM "bricktracker_parts" FROM "bricktracker_parts"
WHERE "bricktracker_parts"."figure" IS NOT NULL WHERE "bricktracker_parts"."figure" IS NOT NULL
GROUP BY GROUP BY
"bricktracker_parts"."id", "bricktracker_parts"."id",
"bricktracker_parts"."figure" "bricktracker_parts"."figure"
) "missing_join" ) "problem_join"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "missing_join"."id" ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "problem_join"."id"
AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing_join"."figure" AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "problem_join"."figure"
{% endblock %} {% endblock %}
{% block group %} {% block group %}

View File

@ -0,0 +1,28 @@
{% extends 'minifigure/base/base.sql' %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block join %}
LEFT JOIN "bricktracker_parts"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_parts"."id"
AND "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "bricktracker_parts"."figure"
{% endblock %}
{% block where %}
WHERE "rebrickable_minifigures"."figure" IN (
SELECT "bricktracker_parts"."figure"
FROM "bricktracker_parts"
WHERE "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
AND "bricktracker_parts"."figure" IS NOT NULL
AND "bricktracker_parts"."damaged" > 0
GROUP BY "bricktracker_parts"."figure"
)
{% endblock %}
{% block group %}
GROUP BY
"rebrickable_minifigures"."figure"
{% endblock %}

View File

@ -4,6 +4,10 @@
SUM("bricktracker_parts"."missing") AS "total_missing", SUM("bricktracker_parts"."missing") AS "total_missing",
{% endblock %} {% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block join %} {% block join %}
LEFT JOIN "bricktracker_parts" LEFT JOIN "bricktracker_parts"
ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_parts"."id" ON "bricktracker_minifigures"."id" IS NOT DISTINCT FROM "bricktracker_parts"."id"

View File

@ -1,7 +1,11 @@
{% extends 'minifigure/base/base.sql' %} {% extends 'minifigure/base/base.sql' %}
{% block total_missing %} {% block total_missing %}
IFNULL("missing_join"."total", 0) AS "total_missing", IFNULL("problem_join"."total_missing", 0) AS "total_missing",
{% endblock %}
{% block total_damaged %}
IFNULL("problem_join"."total_damaged", 0) AS "total_damaged",
{% endblock %} {% endblock %}
{% block total_quantity %} {% block total_quantity %}
@ -17,12 +21,13 @@ COUNT(DISTINCT "bricktracker_minifigures"."id") AS "total_sets"
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
"bricktracker_parts"."figure", "bricktracker_parts"."figure",
SUM("bricktracker_parts"."missing") AS "total" SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("bricktracker_parts"."damaged") AS "total_damaged"
FROM "bricktracker_parts" FROM "bricktracker_parts"
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
GROUP BY "bricktracker_parts"."figure" GROUP BY "bricktracker_parts"."figure"
) "missing_join" ) "problem_join"
ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "missing_join"."figure" ON "rebrickable_minifigures"."figure" IS NOT DISTINCT FROM "problem_join"."figure"
{% endblock %} {% endblock %}
{% block where %} {% block where %}

View File

@ -23,6 +23,9 @@ SELECT
{% block total_missing %} {% block total_missing %}
NULL AS "total_missing", -- dummy for order: total_missing NULL AS "total_missing", -- dummy for order: total_missing
{% endblock %} {% endblock %}
{% block total_damaged %}
NULL AS "total_damaged", -- dummy for order: total_damaged
{% endblock %}
{% block total_quantity %} {% block total_quantity %}
NULL AS "total_quantity", -- dummy for order: total_quantity NULL AS "total_quantity", -- dummy for order: total_quantity
{% endblock %} {% endblock %}

View File

@ -4,6 +4,10 @@
SUM("bricktracker_parts"."missing") AS "total_missing", SUM("bricktracker_parts"."missing") AS "total_missing",
{% endblock %} {% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block total_quantity %} {% block total_quantity %}
SUM("bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity", SUM("bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
{% endblock %} {% endblock %}

View File

@ -5,6 +5,10 @@
SUM("bricktracker_parts"."missing") AS "total_missing", SUM("bricktracker_parts"."missing") AS "total_missing",
{% endblock %} {% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block where %} {% block where %}
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
{% endblock %} {% endblock %}

View File

@ -1,8 +1,9 @@
{% extends 'part/base/base.sql' %} {% extends 'part/base/base.sql' %}
{% block total_missing %} {% block total_missing %}{% endblock %}
{% endblock %}
{% block total_damaged %}{% endblock %}
{% block where %} {% block where %}
WHERE "rebrickable_parts"."print" IS NOT DISTINCT FROM :print WHERE "rebrickable_parts"."print" IS NOT DISTINCT FROM :print

View File

@ -4,6 +4,10 @@
SUM("bricktracker_parts"."missing") AS "total_missing", SUM("bricktracker_parts"."missing") AS "total_missing",
{% endblock %} {% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block total_sets %} {% block total_sets %}
COUNT("bricktracker_parts"."id") - COUNT("bricktracker_parts"."figure") AS "total_sets", COUNT("bricktracker_parts"."id") - COUNT("bricktracker_parts"."figure") AS "total_sets",
{% endblock %} {% endblock %}
@ -20,6 +24,7 @@ AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM "bricktracker_minifigures
{% block where %} {% block where %}
WHERE "bricktracker_parts"."missing" > 0 WHERE "bricktracker_parts"."missing" > 0
OR "bricktracker_parts"."damaged" > 0
{% endblock %} {% endblock %}
{% block group %} {% block group %}

View File

@ -5,6 +5,10 @@
IFNULL("bricktracker_parts"."missing", 0) AS "total_missing", IFNULL("bricktracker_parts"."missing", 0) AS "total_missing",
{% endblock %} {% endblock %}
{% block total_damaged %}
IFNULL("bricktracker_parts"."damaged", 0) AS "total_damaged",
{% endblock %}
{% block where %} {% block where %}
WHERE "bricktracker_parts"."id" IS NOT DISTINCT FROM :id WHERE "bricktracker_parts"."id" IS NOT DISTINCT FROM :id
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure

View File

@ -4,6 +4,10 @@
SUM("bricktracker_parts"."missing") AS "total_missing", SUM("bricktracker_parts"."missing") AS "total_missing",
{% endblock %} {% endblock %}
{% block total_damaged %}
SUM("bricktracker_parts"."damaged") AS "total_damaged",
{% endblock %}
{% block total_quantity %} {% block total_quantity %}
SUM((NOT "bricktracker_parts"."spare") * "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity", SUM((NOT "bricktracker_parts"."spare") * "bricktracker_parts"."quantity" * IFNULL("bricktracker_minifigures"."quantity", 1)) AS "total_quantity",
{% endblock %} {% endblock %}

View File

@ -0,0 +1,7 @@
UPDATE "bricktracker_parts"
SET "damaged" = :damaged
WHERE "bricktracker_parts"."id" IS NOT DISTINCT FROM :id
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
AND "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
AND "bricktracker_parts"."spare" IS NOT DISTINCT FROM :spare

View File

@ -21,6 +21,9 @@ SELECT
{% block total_missing %} {% block total_missing %}
NULL AS "total_missing", -- dummy for order: total_missing NULL AS "total_missing", -- dummy for order: total_missing
{% endblock %} {% endblock %}
{% block total_damaged %}
NULL AS "total_damaged", -- dummy for order: total_damaged
{% endblock %}
{% block total_quantity %} {% block total_quantity %}
NULL AS "total_quantity", -- dummy for order: total_quantity NULL AS "total_quantity", -- dummy for order: total_quantity
{% endblock %} {% endblock %}

View File

@ -5,7 +5,11 @@
{% endblock %} {% endblock %}
{% block total_missing %} {% block total_missing %}
IFNULL("missing_join"."total", 0) AS "total_missing", IFNULL("problem_join"."total_missing", 0) AS "total_missing",
{% endblock %}
{% block total_damaged %}
IFNULL("problem_join"."total_damaged", 0) AS "total_damaged",
{% endblock %} {% endblock %}
{% block total_quantity %} {% block total_quantity %}
@ -32,12 +36,13 @@ ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "bricktracker_set_tags"."id"
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
"bricktracker_parts"."id", "bricktracker_parts"."id",
SUM("bricktracker_parts"."missing") AS "total" SUM("bricktracker_parts"."missing") AS "total_missing",
SUM("bricktracker_parts"."damaged") AS "total_damaged"
FROM "bricktracker_parts" FROM "bricktracker_parts"
{% block where_missing %}{% endblock %} {% block where_missing %}{% endblock %}
GROUP BY "bricktracker_parts"."id" GROUP BY "bricktracker_parts"."id"
) "missing_join" ) "problem_join"
ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "missing_join"."id" ON "bricktracker_sets"."id" IS NOT DISTINCT FROM "problem_join"."id"
-- LEFT JOIN + SELECT to avoid messing the total -- LEFT JOIN + SELECT to avoid messing the total
LEFT JOIN ( LEFT JOIN (

View File

@ -0,0 +1,11 @@
{% extends 'set/base/full.sql' %}
{% block where %}
WHERE "bricktracker_sets"."id" IN (
SELECT "bricktracker_parts"."id"
FROM "bricktracker_parts"
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
AND "bricktracker_parts"."missing" > 0
GROUP BY "bricktracker_parts"."id"
)
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends 'set/base/full.sql' %}
{% block where %}
WHERE "bricktracker_sets"."id" IN (
SELECT "bricktracker_parts"."id"
FROM "bricktracker_parts"
WHERE "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
AND "bricktracker_parts"."damaged" > 0
GROUP BY "bricktracker_parts"."id"
)
{% endblock %}

View File

@ -27,4 +27,5 @@ def details(*, figure: str) -> str:
item=BrickMinifigure().select_generic(figure), item=BrickMinifigure().select_generic(figure),
using=BrickSetList().using_minifigure(figure), using=BrickSetList().using_minifigure(figure),
missing=BrickSetList().missing_minifigure(figure), missing=BrickSetList().missing_minifigure(figure),
damaged=BrickSetList().damaged_minifigure(figure),
) )

View File

@ -19,13 +19,13 @@ def list() -> str:
) )
# Missing # Problem
@part_page.route('/missing', methods=['GET']) @part_page.route('/problem', methods=['GET'])
@exception_handler(__file__) @exception_handler(__file__)
def missing() -> str: def problem() -> str:
return render_template( return render_template(
'missing.html', 'problem.html',
table_collection=BrickPartList().missing() table_collection=BrickPartList().problem()
) )
@ -46,6 +46,10 @@ def details(*, part: str, color: int) -> str:
part, part,
color color
), ),
sets_damaged=BrickSetList().damaged_part(
part,
color
),
minifigures_using=BrickMinifigureList().using_part( minifigures_using=BrickMinifigureList().using_part(
part, part,
color color
@ -54,5 +58,9 @@ def details(*, part: str, color: int) -> str:
part, part,
color color
), ),
minifigures_damaged=BrickMinifigureList().damaged_part(
part,
color
),
similar_prints=BrickPartList().from_print(brickpart) similar_prints=BrickPartList().from_print(brickpart)
) )

View File

@ -136,18 +136,19 @@ def details(*, id: str) -> str:
) )
# Update the missing pieces of a part # Update problematic pieces of a set
@set_page.route('/<id>/parts/<part>/<int:color>/<int:spare>/missing', defaults={'figure': None}, methods=['POST']) # noqa: E501 @set_page.route('/<id>/parts/<part>/<int:color>/<int:spare>/<problem>', defaults={'figure': None}, methods=['POST']) # noqa: E501
@set_page.route('/<id>/minifigures/<figure>/parts/<part>/<int:color>/<int:spare>/missing', methods=['POST']) # noqa: E501 @set_page.route('/<id>/minifigures/<figure>/parts/<part>/<int:color>/<int:spare>/<problem>', methods=['POST']) # noqa: E501
@login_required @login_required
@exception_handler(__file__, json=True) @exception_handler(__file__, json=True)
def missing_part( def problem_part(
*, *,
id: str, id: str,
figure: str | None, figure: str | None,
part: str, part: str,
color: int, color: int,
spare: int, spare: int,
problem: str,
) -> Response: ) -> Response:
brickset = BrickSet().select_specific(id) brickset = BrickSet().select_specific(id)
@ -164,20 +165,21 @@ def missing_part(
minifigure=brickminifigure, minifigure=brickminifigure,
) )
brickpart.update_missing(request.json) amount = brickpart.update_problem(problem, request.json)
# Info # Info
logger.info('Set {set} ({id}): updated part ({part} color: {color}, spare: {spare}, minifigure: {figure}) missing count to {missing}'.format( # noqa: E501 logger.info('Set {set} ({id}): updated part ({part} color: {color}, spare: {spare}, minifigure: {figure}) {problem} count to {amount}'.format( # noqa: E501
set=brickset.fields.set, set=brickset.fields.set,
id=brickset.fields.id, id=brickset.fields.id,
figure=figure, figure=figure,
part=brickpart.fields.part, part=brickpart.fields.part,
color=brickpart.fields.color, color=brickpart.fields.color,
spare=brickpart.fields.spare, spare=brickpart.fields.spare,
missing=brickpart.fields.missing, problem=problem,
amount=amount
)) ))
return jsonify({'missing': brickpart.fields.missing}) return jsonify({problem: amount})
# Refresh a set # Refresh a set

View File

@ -43,7 +43,7 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro table(table_collection, title, id, parent, target, quantity=none, icon=none, image=none, alt=none, details=none, no_missing=none, read_only=none) %} {% macro table(table_collection, title, id, parent, target, quantity=none, icon=none, image=none, alt=none, details=none, read_only=none) %}
{% set size=table_collection | length %} {% set size=table_collection | length %}
{% if size %} {% if size %}
{{ header(title, id, parent, quantity=quantity, icon=icon, class='p-0', image=image, alt=alt) }} {{ header(title, id, parent, quantity=quantity, icon=icon, class='p-0', image=image, alt=alt) }}

View File

@ -90,6 +90,10 @@
{{ badge(check=theme, solo=solo, last=last, color='primary', icon='price-tag-3-line', text=text, alt='Theme', tooltip=tooltip) }} {{ badge(check=theme, solo=solo, last=last, color='primary', icon='price-tag-3-line', text=text, alt='Theme', tooltip=tooltip) }}
{% endmacro %} {% endmacro %}
{% macro total_damaged(damaged, solo=false, last=false) %}
{{ badge(check=damaged, solo=solo, last=last, color='danger', icon='error-warning-line', collapsible='Damaged:', text=damaged, alt='Damaged') }}
{% endmacro %}
{% macro total_quantity(quantity, solo=false, last=false) %} {% macro total_quantity(quantity, solo=false, last=false) %}
{{ badge(check=quantity, solo=solo, last=last, color='success', icon='functions', collapsible='Quantity:', text=quantity, alt='Quantity') }} {{ badge(check=quantity, solo=solo, last=last, color='success', icon='functions', collapsible='Quantity:', text=quantity, alt='Quantity') }}
{% endmacro %} {% endmacro %}
@ -99,7 +103,7 @@
{% endmacro %} {% endmacro %}
{% macro total_missing(missing, solo=false, last=false) %} {% macro total_missing(missing, solo=false, last=false) %}
{{ badge(check=missing, solo=solo, last=last, color='danger', icon='error-warning-line', collapsible='Missing:', text=missing, alt='Missing') }} {{ badge(check=missing, solo=solo, last=last, color='light text-danger-emphasis bg-danger-subtle border border-danger-subtle', icon='question-line', collapsible='Missing:', text=missing, alt='Missing') }}
{% endmacro %} {% endmacro %}
{% macro total_sets(sets, solo=false, last=false) %} {% macro total_sets(sets, solo=false, last=false) %}

View File

@ -1,4 +1,4 @@
{% macro header(color=false, quantity=false, missing=false, missing_parts=false, sets=false, minifigures=false) %} {% macro header(color=false, quantity=false, missing_parts=false, damaged_parts=false, sets=false, minifigures=false) %}
<thead> <thead>
<tr> <tr>
<th data-table-no-sort="true" class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th> <th data-table-no-sort="true" class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th>
@ -9,12 +9,8 @@
{% if quantity %} {% if quantity %}
<th data-table-number="true" scope="col"><i class="ri-functions fw-normal"></i> Quantity</th> <th data-table-number="true" scope="col"><i class="ri-functions fw-normal"></i> Quantity</th>
{% endif %} {% endif %}
{% if missing %} <th data-table-number="true" scope="col"><i class="ri-question-line fw-normal"></i> Missing{% if missing_parts %} parts{% endif %}</th>
<th data-table-number="true" scope="col"><i class="ri-error-warning-line fw-normal"></i> Missing</th> <th data-table-number="true" scope="col"><i class="ri-error-warning-line fw-normal"></i> Damaged{% if damaged_parts %} parts{% endif %}</th>
{% endif %}
{% if missing_parts %}
<th data-table-number="true" scope="col"><i class="ri-error-warning-line fw-normal"></i> Missing parts</th>
{% endif %}
{% if sets %} {% if sets %}
<th data-table-number="true" scope="col"><i class="ri-hashtag fw-normal"></i> Sets</th> <th data-table-number="true" scope="col"><i class="ri-hashtag fw-normal"></i> Sets</th>
{% endif %} {% endif %}

View File

@ -13,6 +13,7 @@
{{ badge.quantity(item.fields.total_quantity, solo=solo, last=last) }} {{ badge.quantity(item.fields.total_quantity, solo=solo, last=last) }}
{{ badge.total_sets(using | length, solo=solo, last=last) }} {{ badge.total_sets(using | length, solo=solo, last=last) }}
{{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }} {{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }}
{{ badge.total_damaged(item.fields.total_damaged, solo=solo, last=last) }}
{% if not last %} {% if not last %}
{{ badge.rebrickable(item, solo=solo, last=last) }} {{ badge.rebrickable(item, solo=solo, last=last) }}
{% endif %} {% endif %}
@ -21,7 +22,8 @@
<div class="accordion accordion-flush" id="minifigure-details"> <div class="accordion accordion-flush" id="minifigure-details">
{{ accordion.table(item.generic_parts(), 'Parts', item.fields.figure, 'minifigure-details', 'part/table.html', icon='shapes-line', alt=item.fields.figure, read_only=read_only)}} {{ accordion.table(item.generic_parts(), 'Parts', item.fields.figure, 'minifigure-details', 'part/table.html', icon='shapes-line', alt=item.fields.figure, read_only=read_only)}}
{{ accordion.cards(using, 'Sets using this minifigure', 'using-inventory', 'minifigure-details', 'set/card.html', icon='hashtag') }} {{ accordion.cards(using, 'Sets using this minifigure', 'using-inventory', 'minifigure-details', 'set/card.html', icon='hashtag') }}
{{ accordion.cards(missing, 'Sets missing parts of this minifigure', 'missing-inventory', 'minifigure-details', 'set/card.html', icon='error-warning-line') }} {{ accordion.cards(missing, 'Sets missing parts for this minifigure', 'missing-inventory', 'minifigure-details', 'set/card.html', icon='question-line') }}
{{ accordion.cards(damaged, 'Sets with damaged parts for this minifigure', 'damaged-inventory', 'minifigure-details', 'set/card.html', icon='error-warning-line') }}
</div> </div>
<div class="card-footer"></div> <div class="card-footer"></div>
{% endif %} {% endif %}

View File

@ -2,7 +2,7 @@
<div class="table-responsive-sm"> <div class="table-responsive-sm">
<table data-table="{% if all %}true{% endif %}" class="table table-striped align-middle" id="minifigures"> <table data-table="{% if all %}true{% endif %}" class="table table-striped align-middle" id="minifigures">
{{ table.header(quantity=true, missing_parts=true, sets=true) }} {{ table.header(quantity=true, missing_parts=true, damaged_parts=true, sets=true) }}
<tbody> <tbody>
{% for item in table_collection %} {% for item in table_collection %}
<tr> <tr>
@ -15,6 +15,7 @@
</td> </td>
<td>{{ item.fields.total_quantity }}</td> <td>{{ item.fields.total_quantity }}</td>
<td>{{ item.fields.total_missing }}</td> <td>{{ item.fields.total_missing }}</td>
<td>{{ item.fields.total_damaged }}</td>
<td>{{ item.fields.total_sets }}</td> <td>{{ item.fields.total_sets }}</td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -15,6 +15,7 @@
{{ badge.total_quantity(item.fields.total_quantity, solo=solo, last=last) }} {{ badge.total_quantity(item.fields.total_quantity, solo=solo, last=last) }}
{{ badge.total_spare(item.fields.total_spare, solo=solo, last=last) }} {{ badge.total_spare(item.fields.total_spare, solo=solo, last=last) }}
{{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }} {{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }}
{{ badge.total_damaged(item.fields.total_damaged, solo=solo, last=last) }}
{% if not last %} {% if not last %}
{{ badge.rebrickable(item, solo=solo, last=last) }} {{ badge.rebrickable(item, solo=solo, last=last) }}
{{ badge.bricklink(item, solo=solo, last=last) }} {{ badge.bricklink(item, solo=solo, last=last) }}
@ -23,9 +24,11 @@
{% if solo %} {% if solo %}
<div class="accordion accordion-flush border-top" id="part-details"> <div class="accordion accordion-flush border-top" id="part-details">
{{ accordion.cards(sets_using, 'Sets using this part', 'sets-using-inventory', 'part-details', 'set/card.html', icon='hashtag') }} {{ accordion.cards(sets_using, 'Sets using this part', 'sets-using-inventory', 'part-details', 'set/card.html', icon='hashtag') }}
{{ accordion.cards(sets_missing, 'Sets missing this part', 'sets-missing-inventory', 'part-details', 'set/card.html', icon='error-warning-line') }} {{ accordion.cards(sets_missing, 'Sets missing this part', 'sets-missing-inventory', 'part-details', 'set/card.html', icon='question-line') }}
{{ accordion.cards(sets_damaged, 'Sets with this part damaged', 'sets-damaged-inventory', 'part-details', 'set/card.html', icon='error-warning-line') }}
{{ accordion.cards(minifigures_using, 'Minifigures using this part', 'minifigures-using-inventory', 'part-details', 'minifigure/card.html', icon='group-line') }} {{ accordion.cards(minifigures_using, 'Minifigures using this part', 'minifigures-using-inventory', 'part-details', 'minifigure/card.html', icon='group-line') }}
{{ accordion.cards(minifigures_missing, 'Minifigures missing this part', 'minifigures-missing-inventory', 'part-details', 'minifigure/card.html', icon='error-warning-line') }} {{ accordion.cards(minifigures_missing, 'Minifigures missing this part', 'minifigures-missing-inventory', 'part-details', 'minifigure/card.html', icon='question-line') }}
{{ accordion.cards(minifigures_damaged, 'Minifigures with this part damaged', 'minifigures-damaged-inventory', 'part-details', 'minifigure/card.html', icon='error-warning-line') }}
{{ accordion.cards(similar_prints, 'Prints using the same base', 'similar-prints', 'part-details', 'part/card.html', icon='paint-brush-line') }} {{ accordion.cards(similar_prints, 'Prints using the same base', 'similar-prints', 'part-details', 'part/card.html', icon='paint-brush-line') }}
</div> </div>
<div class="card-footer"></div> <div class="card-footer"></div>

View File

@ -3,7 +3,7 @@
<div class="table-responsive-sm"> <div class="table-responsive-sm">
<table data-table="{% if all %}true{% endif %}" class="table table-striped align-middle {% if not all %}sortable mb-0{% endif %}" {% if all %}id="parts"{% endif %}> <table data-table="{% if all %}true{% endif %}" class="table table-striped align-middle {% if not all %}sortable mb-0{% endif %}" {% if all %}id="parts"{% endif %}>
{{ table.header(color=true, quantity=not no_quantity, missing=not no_missing, sets=all, minifigures=all) }} {{ table.header(color=true, quantity=not no_quantity, sets=all, minifigures=all) }}
<tbody> <tbody>
{% for item in table_collection %} {% for item in table_collection %}
<tr> <tr>
@ -27,11 +27,12 @@
<td>{% if quantity %}{{ item.fields.quantity * quantity }}{% else %}{{ item.fields.quantity }}{% endif %}</td> <td>{% if quantity %}{{ item.fields.quantity * quantity }}{% else %}{{ item.fields.quantity }}{% endif %}</td>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if not no_missing %}
<td data-sort="{{ item.fields.total_missing }}" class="table-td-input"> <td data-sort="{{ item.fields.total_missing }}" class="table-td-input">
{{ form.input('Missing', item.fields.id, item.html_id(), item.url_for_missing(), item.fields.total_missing, all=all, read_only=read_only) }} {{ form.input('Missing', item.fields.id, item.html_id('missing'), item.url_for_problem('missing'), item.fields.total_missing, all=all, read_only=read_only) }}
</td>
<td data-sort="{{ item.fields.total_damaged }}" class="table-td-input">
{{ form.input('Damaged', item.fields.id, item.html_id('damaged'), item.url_for_problem('damaged'), item.fields.total_damaged, all=all, read_only=read_only) }}
</td> </td>
{% endif %}
{% if all %} {% if all %}
<td>{{ item.fields.total_sets }}</td> <td>{{ item.fields.total_sets }}</td>
<td>{{ item.fields.total_minifigures }}</td> <td>{{ item.fields.total_minifigures }}</td>

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %} - Missing parts{% endblock %} {% block title %} - Problematic parts{% endblock %}
{% block main %} {% block main %}
<div class="container-fluid px-0"> <div class="container-fluid px-0">

View File

@ -6,8 +6,11 @@
<div {% if not solo %}id="set-{{ item.fields.id }}"{% endif %} class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}" <div {% if not solo %}id="set-{{ item.fields.id }}"{% endif %} class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}"
{% if not solo %} {% if not solo %}
data-index="{{ index }}" data-number="{{ item.fields.set }}" data-name="{{ item.fields.name | lower }}" data-parts="{{ item.fields.number_of_parts }}" data-index="{{ index }}" data-number="{{ item.fields.set }}" data-name="{{ item.fields.name | lower }}" data-parts="{{ item.fields.number_of_parts }}"
data-year="{{ item.fields.year }}" data-theme="{{ item.theme.name | lower }}" data-minifigures="{{ item.fields.total_minifigures }}" data-has-minifigures="{{ (item.fields.total_minifigures > 0) | int }}" data-year="{{ item.fields.year }}" data-theme="{{ item.theme.name | lower }}"
data-has-missing="{{ (item.fields.total_missing > 0) | int }}" data-has-missing-instructions="{{ (not (item.instructions | length)) | int }}" data-missing="{{ item.fields.total_missing }}" data-has-missing-instructions="{{ (not (item.instructions | length)) | int }}"
data-has-minifigures="{{ (item.fields.total_minifigures > 0) | int }}" data-minifigures="{{ item.fields.total_minifigures }}"
data-has-missing="{{ (item.fields.total_missing > 0) | int }}" data-missing="{{ item.fields.total_missing }}"
data-has-damaged="{{ (item.fields.total_damaged > 0) | int }}" data-damaged="{{ item.fields.total_damaged }}"
{% for status in brickset_statuses %} {% for status in brickset_statuses %}
{% with checked=item.fields[status.as_column()] %} {% with checked=item.fields[status.as_column()] %}
{% if checked %} {% if checked %}
@ -42,6 +45,7 @@
{{ badge.parts(item.fields.number_of_parts, solo=solo, last=last) }} {{ badge.parts(item.fields.number_of_parts, solo=solo, last=last) }}
{{ badge.total_minifigures(item.fields.total_minifigures, solo=solo, last=last) }} {{ badge.total_minifigures(item.fields.total_minifigures, solo=solo, last=last) }}
{{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }} {{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }}
{{ badge.total_damaged(item.fields.total_damaged, solo=solo, last=last) }}
{% for owner in brickset_owners %} {% for owner in brickset_owners %}
{{ badge.owner(item, owner, solo=solo, last=last) }} {{ badge.owner(item, owner, solo=solo, last=last) }}
{% endfor %} {% endfor %}

View File

@ -29,7 +29,9 @@
<button id="sort-parts" type="button" class="btn btn-outline-primary" <button id="sort-parts" type="button" class="btn btn-outline-primary"
data-sort-attribute="parts" data-sort-desc="true"><i class="ri-shapes-line"></i><span class="d-none d-xxl-inline"> Parts</span></button> data-sort-attribute="parts" data-sort-desc="true"><i class="ri-shapes-line"></i><span class="d-none d-xxl-inline"> Parts</span></button>
<button id="sort-missing" type="button" class="btn btn-outline-primary" <button id="sort-missing" type="button" class="btn btn-outline-primary"
data-sort-attribute="missing" data-sort-desc="true"><i class="ri-error-warning-line"></i><span class="d-none d-xxl-inline"> Missing</span></button> data-sort-attribute="missing" data-sort-desc="true"><i class="ri-question-line"></i><span class="d-none d-xxl-inline"> Missing</span></button>
<button id="sort-damaged" type="button" class="btn btn-outline-primary"
data-sort-attribute="damaged" data-sort-desc="true"><i class="ri-error-warning-line"></i><span class="d-none d-xxl-inline"> Damaged</span></button>
<button id="sort-clear" type="button" class="btn btn-outline-dark" <button id="sort-clear" type="button" class="btn btn-outline-dark"
data-sort-clear="true"><i class="ri-close-circle-line"></i><span class="d-none d-xxl-inline"> Clear</span></button> data-sort-clear="true"><i class="ri-close-circle-line"></i><span class="d-none d-xxl-inline"> Clear</span></button>
</div> </div>
@ -53,6 +55,7 @@
<option value="" selected>All</option> <option value="" selected>All</option>
<option value="-has-missing">Set is complete</option> <option value="-has-missing">Set is complete</option>
<option value="has-missing">Set has missing pieces</option> <option value="has-missing">Set has missing pieces</option>
<option value="has-damaged">Set has damaged pieces</option>
<option value="has-missing-instructions">Set has missing instructions</option> <option value="has-missing-instructions">Set has missing instructions</option>
{% for status in brickset_statuses %} {% for status in brickset_statuses %}
<option value="{{ status.as_dataset() }}">{{ status.fields.name }}</option> <option value="{{ status.as_dataset() }}">{{ status.fields.name }}</option>