1use std::{fmt, str::FromStr};
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
7
8use crate::{Money, Object, ParseError};
9
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct PartyId(String);
13
14impl PartyId {
15 pub fn as_str(&self) -> &str {
17 &self.0
18 }
19}
20
21impl FromStr for PartyId {
22 type Err = ParseError;
23
24 fn from_str(s: &str) -> Result<Self, Self::Err> {
25 if crate::is_uuid(s) { Ok(PartyId(s.to_owned())) } else { Err(ParseError::Party(s.to_owned())) }
26 }
27}
28
29impl fmt::Display for PartyId {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.write_str(&self.0)
32 }
33}
34
35impl Serialize for PartyId {
36 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37 serializer.serialize_str(&self.0)
38 }
39}
40
41impl<'de> Deserialize<'de> for PartyId {
42 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43 String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct Country([u8; 2]);
50
51impl Country {
52 pub fn as_str(&self) -> &str {
54 std::str::from_utf8(&self.0).unwrap_or("??")
56 }
57}
58
59impl FromStr for Country {
60 type Err = ParseError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 match s.as_bytes() {
64 &[a, b] if a.is_ascii_uppercase() && b.is_ascii_uppercase() => Ok(Country([a, b])),
65 _ => Err(ParseError::Country(s.to_owned())),
66 }
67 }
68}
69
70impl fmt::Display for Country {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.write_str(self.as_str())
73 }
74}
75
76impl Serialize for Country {
77 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
78 serializer.serialize_str(self.as_str())
79 }
80}
81
82impl<'de> Deserialize<'de> for Country {
83 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
84 String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
90pub struct Registration {
91 pub register: String,
93 #[serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "crate::present_text")]
95 pub authority: Option<String>,
96 pub number: String,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
102pub struct TaxId {
103 pub scheme: String,
105 pub number: String,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
112pub struct Shares {
113 #[serde(deserialize_with = "count")]
115 pub count: u64,
116 #[serde(deserialize_with = "nominal")]
118 pub nominal: Money,
119}
120
121fn count<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
122 match crate::safe_u64(deserializer)? {
123 0 => Err(de::Error::custom("at least one share")),
124 count => Ok(count),
125 }
126}
127
128fn nominal<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Money, D::Error> {
129 let nominal = Money::deserialize(deserializer)?;
130 if nominal.base_units() <= 0 {
131 return Err(de::Error::custom("a nominal amount is above zero"));
132 }
133 Ok(nominal)
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
138pub struct Organisation {
139 pub id: PartyId,
141 pub name: String,
143 pub legal_form: String,
145 pub country: Country,
147 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "crate::non_empty")]
149 pub registrations: Vec<Registration>,
150 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "crate::non_empty")]
152 pub tax_ids: Vec<TaxId>,
153 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "crate::non_empty")]
155 pub shares: Vec<Shares>,
156}
157
158impl Organisation {
159 pub fn capital(&self) -> Option<Money> {
161 let currency = self.shares.first()?.nominal.currency().clone();
162 let mut total: i128 = 0;
163 for shares in &self.shares {
164 if *shares.nominal.currency() != currency {
165 return None;
166 }
167 total = total.checked_add(i128::from(shares.count).checked_mul(shares.nominal.base_units())?)?;
168 }
169 Money::new(total, currency)
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
175#[serde(rename_all = "snake_case")]
176pub enum RoleKind {
177 Owner,
179 Shareholder,
181 Partner,
183 ManagingDirector,
185 BoardMember,
187 Member,
189 AuthorisedSignatory,
191 #[serde(other, skip_serializing)]
193 Unknown,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
198pub struct Role {
199 pub organisation: PartyId,
201 pub kind: RoleKind,
203 #[serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "held")]
206 pub shares: Option<Shares>,
207}
208
209fn held<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Shares>, D::Error> {
210 Shares::deserialize(deserializer).map(Some)
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
215pub struct Person {
216 pub id: PartyId,
218 pub name: String,
220 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "crate::non_empty")]
222 pub tax_ids: Vec<TaxId>,
223 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "crate::non_empty")]
225 pub roles: Vec<Role>,
226}
227
228impl Object for Organisation {
229 const TYPE: &'static str = "organisation";
230}
231
232impl Object for Person {
233 const TYPE: &'static str = "person";
234}
235
236impl Object for Registration {
237 const TYPE: &'static str = "registration";
238}
239
240impl Object for TaxId {
241 const TYPE: &'static str = "tax_id";
242}
243
244impl Object for Shares {
245 const TYPE: &'static str = "shares";
246}
247
248impl Object for Role {
249 const TYPE: &'static str = "role";
250}
251
252#[cfg(test)]
253pub(crate) mod tests {
254 use super::*;
255 use crate::{Money, from_root, to_root};
256
257 pub(crate) fn party(n: u8) -> PartyId {
258 format!("00000000-0000-7000-8000-0000000000{n:02x}").parse().unwrap()
259 }
260
261 pub(crate) fn organisation(n: u8) -> Organisation {
262 Organisation {
263 id: party(n),
264 name: "Example Trading GmbH".into(),
265 legal_form: "de-gmbh".into(),
266 country: "DE".parse().unwrap(),
267 registrations: vec![Registration {
268 register: "de-hrb".into(),
269 authority: Some("Amtsgericht Musterstadt".into()),
270 number: "HRB 12345".into(),
271 }],
272 tax_ids: vec![TaxId { scheme: "vat".into(), number: "DE000000000".into() }],
273 shares: vec![two_euro_shares(512)],
274 }
275 }
276
277 pub(crate) fn two_euro_shares(count: u64) -> Shares {
278 Shares { count, nominal: Money::new(200, "EUR".parse().unwrap()).unwrap() }
279 }
280
281 #[test]
282 fn the_capital_follows_from_the_shares() {
283 assert_eq!(organisation(1).capital(), Money::new(102_400, "EUR".parse().unwrap()));
284 let none = Organisation { shares: vec![], ..organisation(1) };
285 assert_eq!(none.capital(), None);
286 }
287
288 #[test]
289 fn an_organisation_as_a_root_object() {
290 let org = organisation(1);
291 let json = to_root(&org).unwrap();
292 let text = String::from_utf8_lossy(&json);
293 assert!(text.starts_with(r#"{"country":"DE","id":"#) && text.ends_with(r#""type":"organisation"}"#), "{text}");
294 assert_eq!(from_root::<Organisation>(&json).unwrap(), org);
295 }
296
297 #[test]
298 fn a_person_with_roles_and_shares() {
299 let json = br#"{"type":"person","id":"00000000-0000-7000-8000-000000000002","name":"Erika Beispiel",
300 "roles":[{"organisation":"00000000-0000-7000-8000-000000000001","kind":"shareholder",
301 "shares":{"count":257,"nominal":{"currency":"EUR","base_units":"200"}}},
302 {"organisation":"00000000-0000-7000-8000-000000000001","kind":"managing_director"}]}"#;
303 let person: Person = from_root(json).unwrap();
304 assert_eq!(person.roles[0].shares, Some(two_euro_shares(257)));
305 assert_eq!(person.roles[1].kind, RoleKind::ManagingDirector);
306 for (from, to) in [("257", "0"), ("257", "2.5"), (r#""200""#, r#""0""#), (r#""200""#, r#""-200""#)] {
307 let bad = String::from_utf8_lossy(json).replace(from, to);
308 assert!(from_root::<Person>(bad.as_bytes()).is_err(), "{to}");
309 }
310 }
311
312 #[test]
313 fn countries_are_two_upper_case_letters() {
314 assert_eq!("DE".parse::<Country>().unwrap().as_str(), "DE");
315 for bad in ["de", "DEU", "D", ""] {
316 assert!(bad.parse::<Country>().is_err(), "{bad}");
317 }
318 }
319}