Skip to main content

teamcraft/domain/
as_built.rs

1//! As-built records: what the deviation of a built unit from its design concerns and how it was decided.
2
3use serde::{Deserialize, Serialize};
4
5use super::ItemType;
6
7impl ItemType {
8    /// Whether a physical unit (an as-built item) can be built to a revision
9    /// of this type.
10    pub fn can_be_built_to(self) -> bool {
11        matches!(
12            self,
13            ItemType::Part | ItemType::Assembly | ItemType::Software
14        )
15    }
16}
17
18/// What a deviation of the built unit from its design concerns.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum DeviationKind {
22    /// A measured dimension or tolerance (a bore too large, a flatness).
23    Dimension,
24    /// Another material, heat treatment or surface finish.
25    Material,
26    /// Another, missing or additional component (also visible in the
27    /// as-built BOM).
28    Component,
29    /// A manufacturing step done differently (welding, torque, bonding).
30    Process,
31    Other,
32}
33
34impl DeviationKind {
35    pub const ALL: [DeviationKind; 5] = [
36        DeviationKind::Dimension,
37        DeviationKind::Material,
38        DeviationKind::Component,
39        DeviationKind::Process,
40        DeviationKind::Other,
41    ];
42
43    pub fn as_str(self) -> &'static str {
44        match self {
45            DeviationKind::Dimension => "dimension",
46            DeviationKind::Material => "material",
47            DeviationKind::Component => "component",
48            DeviationKind::Process => "process",
49            DeviationKind::Other => "other",
50        }
51    }
52
53    pub fn label(self) -> &'static str {
54        match self {
55            DeviationKind::Dimension => "Dimension",
56            DeviationKind::Material => "Material",
57            DeviationKind::Component => "Component",
58            DeviationKind::Process => "Process",
59            DeviationKind::Other => "Other",
60        }
61    }
62
63    pub fn parse(s: &str) -> Option<DeviationKind> {
64        DeviationKind::ALL
65            .into_iter()
66            .find(|k| k.as_str() == s.trim().to_ascii_lowercase())
67    }
68}
69
70/// The decision about a deviation (the disposition of a nonconformance).
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(rename_all = "snake_case")]
73pub enum Disposition {
74    /// Not decided yet: the as-built revision cannot be submitted for release.
75    Open,
76    /// Accepted as it is (a concession).
77    UseAsIs,
78    /// Reworked to the design.
79    Rework,
80    /// Repaired: usable, but still not to the design.
81    Repair,
82    /// The unit or the component is scrapped.
83    Scrap,
84}
85
86impl Disposition {
87    pub const ALL: [Disposition; 5] = [
88        Disposition::Open,
89        Disposition::UseAsIs,
90        Disposition::Rework,
91        Disposition::Repair,
92        Disposition::Scrap,
93    ];
94
95    pub fn as_str(self) -> &'static str {
96        match self {
97            Disposition::Open => "open",
98            Disposition::UseAsIs => "use_as_is",
99            Disposition::Rework => "rework",
100            Disposition::Repair => "repair",
101            Disposition::Scrap => "scrap",
102        }
103    }
104
105    pub fn label(self) -> &'static str {
106        match self {
107            Disposition::Open => "Open",
108            Disposition::UseAsIs => "Use as is",
109            Disposition::Rework => "Rework",
110            Disposition::Repair => "Repair",
111            Disposition::Scrap => "Scrap",
112        }
113    }
114
115    /// Also accepts `use-as-is` and `use as is`; empty means open.
116    pub fn parse(s: &str) -> Option<Disposition> {
117        let s = s.trim().to_ascii_lowercase().replace(['-', ' '], "_");
118        if s.is_empty() {
119            return Some(Disposition::Open);
120        }
121        Disposition::ALL.into_iter().find(|d| d.as_str() == s)
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::domain::{NumberContext, NumberScheme};
129
130    #[test]
131    fn as_built_type_and_enums() {
132        let ctx = NumberContext {
133            project: "ROVER",
134            item_type: ItemType::AsBuilt,
135            year: 2026,
136            month: 11,
137        };
138        assert_eq!(NumberScheme::default_scheme().format(&ctx, 7), "AB-000007");
139        assert_eq!(ItemType::parse("As-Built"), Some(ItemType::AsBuilt));
140        assert_eq!(ItemType::parse("as built"), Some(ItemType::AsBuilt));
141        assert!(ItemType::Assembly.can_be_built_to());
142        assert!(!ItemType::AsBuilt.can_be_built_to());
143        assert!(!ItemType::Simulation.can_be_built_to());
144        assert!(!ItemType::Document.can_be_built_to());
145        for k in DeviationKind::ALL {
146            assert_eq!(DeviationKind::parse(k.as_str()), Some(k));
147        }
148        assert_eq!(
149            DeviationKind::parse(" Dimension "),
150            Some(DeviationKind::Dimension)
151        );
152        assert_eq!(DeviationKind::parse("colour"), None);
153        for d in Disposition::ALL {
154            assert_eq!(Disposition::parse(d.as_str()), Some(d));
155        }
156        assert_eq!(Disposition::parse("Use-as-is"), Some(Disposition::UseAsIs));
157        assert_eq!(Disposition::parse("use as is"), Some(Disposition::UseAsIs));
158        assert_eq!(Disposition::parse(""), Some(Disposition::Open));
159        assert_eq!(Disposition::parse("ship it"), None);
160    }
161
162    /// The labels shown on the as-built pages (found by cargo mutants).
163    #[test]
164    fn labels() {
165        let kinds: Vec<&str> = DeviationKind::ALL.iter().map(|k| k.label()).collect();
166        assert_eq!(
167            kinds,
168            ["Dimension", "Material", "Component", "Process", "Other"]
169        );
170        let dispositions: Vec<&str> = Disposition::ALL.iter().map(|d| d.label()).collect();
171        assert_eq!(
172            dispositions,
173            ["Open", "Use as is", "Rework", "Repair", "Scrap"]
174        );
175    }
176}