Deck status (Idea/Building/Built/Retired) and pulled-card tracking
Only building and built decks now claim owned copies, so several idea decks can share one copy without an over-assignment warning. Deck cards track how many copies are physically pulled: physical decks list cards to pull with their source (a storage box, or another deck to take a copy from), and idea decks split cards into available / in other decks / not owned. The deck list shows status chips with to-pull counts, filters by status, and collapses retired decks. Status and pulled counts sync, which bumps the library document to version 4. Existing decks migrate as ideas. Closes #25 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -188,12 +188,19 @@ pub struct LibraryCard {
|
||||
pub quantity_owned: i64,
|
||||
/// Names of decks this card is currently assigned to.
|
||||
pub decks: Vec<String>,
|
||||
/// Total copies assigned across all decks. If it exceeds `quantity_owned`,
|
||||
/// the physical copies are over-committed.
|
||||
/// Copies claimed by building/built decks' main boards (idea and retired
|
||||
/// decks claim nothing). If it exceeds `quantity_owned`, the physical
|
||||
/// copies are over-committed.
|
||||
pub quantity_assigned: i64,
|
||||
/// Free-text physical storage location (e.g. "Box 1"). Meaningful mainly for
|
||||
/// cards not currently in a deck.
|
||||
pub location: String,
|
||||
/// The subset of `decks` that physically exist (building or built).
|
||||
#[serde(default)]
|
||||
pub physical_decks: Vec<String>,
|
||||
/// Copies physically pulled into decks; the rest are at `location`.
|
||||
#[serde(default)]
|
||||
pub quantity_in_decks: i64,
|
||||
}
|
||||
|
||||
/// A card in a deck, whether or not the user owns it.
|
||||
@@ -218,6 +225,75 @@ pub struct DeckEntry {
|
||||
/// zone with a second commander.
|
||||
#[serde(default)]
|
||||
pub partner: Option<crate::partner::Partner>,
|
||||
/// Copies physically in this deck; the rest are still to be pulled. Only
|
||||
/// meaningful for building/built decks.
|
||||
#[serde(default)]
|
||||
pub quantity_pulled: i64,
|
||||
/// Owned copies not claimed by any *other* building/built deck.
|
||||
#[serde(default)]
|
||||
pub quantity_spare: i64,
|
||||
/// Owned copies not pulled into any deck — still in their box.
|
||||
#[serde(default)]
|
||||
pub quantity_in_box: i64,
|
||||
/// The *other* building/built decks claiming copies — those with copies
|
||||
/// pulled are where to take one from when none are left in the box.
|
||||
#[serde(default)]
|
||||
pub claimed_by: Vec<DeckHolding>,
|
||||
/// Where the card's library copies are stored (e.g. "Box 1").
|
||||
#[serde(default)]
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
/// A building/built deck's claim on a card's copies.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DeckHolding {
|
||||
pub deck_id: i64,
|
||||
pub name: String,
|
||||
/// Copies the deck's main board lists.
|
||||
pub quantity: i64,
|
||||
/// Of those, copies physically in the deck.
|
||||
pub pulled: i64,
|
||||
}
|
||||
|
||||
/// Where a deck stands physically. Only building and built decks hold cards
|
||||
/// and claim owned copies; ideas and retired decks are just lists.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DeckStatus {
|
||||
#[default]
|
||||
Idea,
|
||||
Building,
|
||||
Built,
|
||||
Retired,
|
||||
}
|
||||
|
||||
/// SQL list of the statuses for which [`DeckStatus::is_physical`] holds.
|
||||
pub const PHYSICAL_STATUSES_SQL: &str = "('building', 'built')";
|
||||
|
||||
impl DeckStatus {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
DeckStatus::Idea => "idea",
|
||||
DeckStatus::Building => "building",
|
||||
DeckStatus::Built => "built",
|
||||
DeckStatus::Retired => "retired",
|
||||
}
|
||||
}
|
||||
|
||||
/// Unknown values (say, from a newer build) read as an idea, which holds
|
||||
/// nothing — the safe direction to be wrong in.
|
||||
pub fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"building" => DeckStatus::Building,
|
||||
"built" => DeckStatus::Built,
|
||||
"retired" => DeckStatus::Retired,
|
||||
_ => DeckStatus::Idea,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_physical(self) -> bool {
|
||||
matches!(self, DeckStatus::Building | DeckStatus::Built)
|
||||
}
|
||||
}
|
||||
|
||||
/// One printing of a card, for the printing picker and copy labels.
|
||||
@@ -272,6 +348,8 @@ pub struct Deck {
|
||||
/// The card whose art headlines the deck in the deck list, if chosen;
|
||||
/// `None` leaves the pick to the UI.
|
||||
pub cover_oracle_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub status: DeckStatus,
|
||||
}
|
||||
|
||||
/// Aggregate stats about the owned library, for a dashboard-style summary.
|
||||
|
||||
Reference in New Issue
Block a user