dpm_interchange/mspdi/report.rs
1//! Per-item interchange reports; they describe a candidate or export and never change state.
2
3use dpm_model::{DependencyId, DependencyKind, Key, WorkItemId, WorkKind, WorkStatus};
4use serde::{Deserialize, Serialize};
5
6/// One field-level observation about a mapped value.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct Finding {
9 /// Source or DPM field the observation concerns.
10 pub field: String,
11 /// What was approximated, rejected or omitted, and why.
12 pub detail: String,
13}
14
15impl Finding {
16 pub(crate) fn new(field: &str, detail: impl Into<String>) -> Self {
17 Self {
18 field: field.into(),
19 detail: detail.into(),
20 }
21 }
22}
23
24/// One local field that the candidate changes on existing work.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct FieldChange {
27 /// DPM field: `title`, `order`, `objective`, `kind`, `parent`, `priority`, `estimate` or
28 /// `calendar`.
29 pub field: String,
30 /// Local value before the import.
31 pub before: String,
32 /// Value in the candidate.
33 pub after: String,
34}
35
36/// One field that the candidate changes on an existing local dependency.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct DependencyChange {
39 /// Stable dependency identity.
40 pub dependency: DependencyId,
41 /// Local relation, as `KIND PREDECESSOR -> SUCCESSOR` with work keys.
42 pub relation: String,
43 /// Dependency field; an import changes only `lag` and `lag_basis`.
44 pub field: String,
45 /// Local value before the import.
46 pub before: String,
47 /// Value in the candidate.
48 pub after: String,
49}
50
51/// Identity of the source project named by the document.
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct SourceSummary {
54 /// Document format; always `mspdi`.
55 pub format: String,
56 /// Source project GUID, when the document records one.
57 pub project_guid: Option<String>,
58 /// Source project `Name`, or its `Title` when the document has no name, when present.
59 pub name: Option<String>,
60 /// What an import carries: plan structure, never calendar dates.
61 pub scope: String,
62}
63
64/// Local work that a source task maps to in the candidate.
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct WorkReference {
67 /// Stable work identity: the task GUID, or an identity derived from the project GUID and UID,
68 /// or from the target project, key prefix and UID; the item's `identity` finding says which.
69 pub id: WorkItemId,
70 /// Work key; existing keys are kept, new work receives `PREFIX-UID`.
71 pub key: Key,
72 /// Mapped work kind.
73 pub kind: WorkKind,
74 /// Lifecycle in the candidate; imports never advance it.
75 pub status: WorkStatus,
76}
77
78/// What the candidate does with one source task.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum ItemOutcome {
81 /// New work is proposed.
82 Created,
83 /// Existing work with the same identity changes.
84 Updated,
85 /// Existing work with the same identity already matches.
86 Unchanged,
87 /// The task is not imported; `rejected` gives the reason.
88 Skipped,
89}
90
91/// Mapping report for one source task.
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct ItemReport {
94 /// Source task UID, unique within the document only.
95 pub uid: i64,
96 /// Source task GUID, when present.
97 pub guid: Option<String>,
98 /// Source task name.
99 pub name: String,
100 /// Candidate action.
101 pub outcome: ItemOutcome,
102 /// Local work in the candidate; absent when skipped.
103 pub work: Option<WorkReference>,
104 /// Source fields carried over exactly.
105 pub preserved: Vec<String>,
106 /// Source fields carried over with a documented loss of precision or meaning, and defaults
107 /// applied to new work where the source omits a field.
108 pub approximated: Vec<Finding>,
109 /// Source data present in the document that the candidate does not carry.
110 pub rejected: Vec<Finding>,
111 /// Fields the source omits or leaves empty, so existing work keeps its local value.
112 pub kept: Vec<String>,
113 /// Every local field the candidate changes on existing work; empty unless `Updated`.
114 pub changes: Vec<FieldChange>,
115}
116
117/// How one source `PredecessorLink` maps to dependencies.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
119pub enum LinkOutcome {
120 /// Relation and lag carried over exactly, and every local dependency it updates already
121 /// matches.
122 Preserved,
123 /// Relation and lag carried over exactly, but a local dependency it updates changes; `changes`
124 /// gives before and after values.
125 Changed,
126 /// Carried over with an approximated lag; `changes` still lists any local dependency change.
127 Approximated,
128 /// Not carried over; `notes` gives the reason.
129 Rejected,
130}
131
132/// Mapping report for one source `PredecessorLink`.
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct LinkReport {
135 /// Predecessor task UID, when the link names one.
136 pub predecessor_uid: Option<i64>,
137 /// Successor task UID: the task that holds the link.
138 pub successor_uid: i64,
139 /// Relation abbreviation, or the raw type code when unsupported.
140 pub relation: Option<String>,
141 /// Source lag in tenths of a minute.
142 pub link_lag: i64,
143 /// Source lag format code.
144 pub lag_format: Option<u32>,
145 /// Candidate action.
146 pub outcome: LinkOutcome,
147 /// Expansion, merge, approximation or rejection details.
148 pub notes: Vec<String>,
149 /// Every field the candidate changes on an existing local dependency this link maps to; empty
150 /// when each one already matches.
151 #[serde(default)]
152 pub changes: Vec<DependencyChange>,
153 /// Candidate dependencies carrying this link.
154 pub dependencies: Vec<DependencyId>,
155}
156
157/// A local dependency between imported work that the source no longer contains.
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub struct RemovedDependency {
160 /// Stable dependency identity.
161 pub id: DependencyId,
162 /// Predecessor work key.
163 pub predecessor: Key,
164 /// Successor work key.
165 pub successor: Key,
166 /// Relation kind.
167 pub kind: DependencyKind,
168 /// Local lag in hours.
169 pub lag_hours: f64,
170}
171
172/// What the candidate does with one source calendar.
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
174pub enum CalendarOutcome {
175 /// A new workspace calendar definition is proposed.
176 Created,
177 /// A workspace calendar of the same name changes to the source definition.
178 Updated,
179 /// A workspace calendar of the same name already has the source definition.
180 Unchanged,
181 /// The source calendar equals a built-in calendar (`standard` or `always`), which it maps to.
182 BuiltIn,
183 /// The calendar is not imported; `rejected` gives the reason.
184 Skipped,
185}
186
187/// Mapping report for one source `<Calendar>`.
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189pub struct CalendarReport {
190 /// Source calendar UID, unique within the document only.
191 pub uid: i64,
192 /// Source calendar name.
193 pub name: String,
194 /// Workspace calendar name in the candidate; absent when skipped.
195 pub calendar: Option<String>,
196 /// Candidate action.
197 pub outcome: CalendarOutcome,
198 /// Source values carried with a documented loss.
199 pub approximated: Vec<Finding>,
200 /// Source data the candidate does not carry, such as recurring exceptions.
201 pub rejected: Vec<Finding>,
202 /// Existing work that follows an `Updated` calendar, whose schedule the update changes
203 /// whether or not the import touches it.
204 #[serde(default, skip_serializing_if = "Vec::is_empty")]
205 pub affected_work: Vec<Key>,
206}
207
208/// Full import report returned with the candidate and its reviewed preview.
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210pub struct ImportReport {
211 /// Source document identity.
212 pub source: SourceSummary,
213 /// Project receiving the imported work.
214 pub target_project: Key,
215 /// One entry per source task, in document order.
216 pub items: Vec<ItemReport>,
217 /// One entry per source link, in document order.
218 pub links: Vec<LinkReport>,
219 /// Local dependencies between imported work that the candidate removes because the source
220 /// omits them.
221 pub removed_dependencies: Vec<RemovedDependency>,
222 /// One entry per source calendar when calendars are imported with a time zone; empty and
223 /// omitted otherwise.
224 #[serde(default, skip_serializing_if = "Vec::is_empty")]
225 pub calendars: Vec<CalendarReport>,
226 /// Document-level data the candidate does not carry, such as resources, and calendars when no
227 /// time zone is given.
228 pub rejected: Vec<Finding>,
229 /// Work in the target project that the source does not name; the candidate keeps it unchanged.
230 pub retained: Vec<Key>,
231}
232
233/// Export report for one work item.
234#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
235pub struct ItemExportReport {
236 /// Work key.
237 pub key: Key,
238 /// Written task UID; unique within this file only.
239 pub uid: u32,
240 /// Written task GUID: the stable work identity.
241 pub guid: String,
242 /// Fields written exactly.
243 pub preserved: Vec<String>,
244 /// Fields written with a documented loss of precision.
245 pub approximated: Vec<Finding>,
246 /// DPM data with no MSPDI representation in the supported subset.
247 pub omitted: Vec<Finding>,
248}
249
250/// Export report for one dependency touching the exported project.
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252pub struct DependencyExportReport {
253 /// Stable dependency identity.
254 pub id: DependencyId,
255 /// Whether the relation is written.
256 pub written: bool,
257 /// DPM attributes with no MSPDI representation, or the reason it is not written.
258 pub notes: Vec<String>,
259}
260
261/// Full export report returned with the document.
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263pub struct ExportReport {
264 /// Exported project key.
265 pub project: Key,
266 /// Written project GUID: the stable project identity.
267 pub project_guid: String,
268 /// One entry per written task, in document order.
269 pub items: Vec<ItemExportReport>,
270 /// Dependencies with at least one endpoint in the project.
271 pub dependencies: Vec<DependencyExportReport>,
272 /// Workspace data outside the supported subset.
273 pub omitted: Vec<Finding>,
274}