1use chrono::{Datelike, NaiveDate};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
9#[serde(rename_all = "snake_case")]
10pub enum TransferPricingMethod {
11 #[default]
13 CostPlus,
14 ComparableUncontrolled,
16 ResalePrice,
18 TransactionalNetMargin,
20 ProfitSplit,
22 FixedFee,
24}
25
26impl TransferPricingMethod {
27 pub fn typical_margin_range(&self) -> (Decimal, Decimal) {
29 match self {
30 Self::CostPlus => (Decimal::new(3, 2), Decimal::new(15, 2)), Self::ComparableUncontrolled => (Decimal::ZERO, Decimal::ZERO), Self::ResalePrice => (Decimal::new(10, 2), Decimal::new(30, 2)), Self::TransactionalNetMargin => (Decimal::new(2, 2), Decimal::new(10, 2)), Self::ProfitSplit => (Decimal::new(40, 2), Decimal::new(60, 2)), Self::FixedFee => (Decimal::ZERO, Decimal::ZERO), }
37 }
38
39 pub fn is_cost_based(&self) -> bool {
41 matches!(self, Self::CostPlus | Self::TransactionalNetMargin)
42 }
43
44 pub fn requires_comparables(&self) -> bool {
46 matches!(
47 self,
48 Self::ComparableUncontrolled | Self::ResalePrice | Self::TransactionalNetMargin
49 )
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct TransferPricingPolicy {
56 pub policy_id: String,
58 pub name: String,
60 pub method: TransferPricingMethod,
62 pub markup_percent: Decimal,
64 pub min_markup_percent: Option<Decimal>,
66 pub max_markup_percent: Option<Decimal>,
68 pub applicable_transaction_types: Vec<String>,
70 pub effective_date: NaiveDate,
72 pub end_date: Option<NaiveDate>,
74 pub fee_currency: Option<String>,
76 pub fixed_fee_amount: Option<Decimal>,
78 pub documentation_requirements: DocumentationLevel,
80 pub requires_annual_benchmarking: bool,
82}
83
84impl TransferPricingPolicy {
85 pub fn new_cost_plus(
87 policy_id: String,
88 name: String,
89 markup_percent: Decimal,
90 effective_date: NaiveDate,
91 ) -> Self {
92 Self {
93 policy_id,
94 name,
95 method: TransferPricingMethod::CostPlus,
96 markup_percent,
97 min_markup_percent: None,
98 max_markup_percent: None,
99 applicable_transaction_types: Vec::new(),
100 effective_date,
101 end_date: None,
102 fee_currency: None,
103 fixed_fee_amount: None,
104 documentation_requirements: DocumentationLevel::Standard,
105 requires_annual_benchmarking: false,
106 }
107 }
108
109 pub fn new_fixed_fee(
111 policy_id: String,
112 name: String,
113 fee_amount: Decimal,
114 currency: String,
115 effective_date: NaiveDate,
116 ) -> Self {
117 Self {
118 policy_id,
119 name,
120 method: TransferPricingMethod::FixedFee,
121 markup_percent: Decimal::ZERO,
122 min_markup_percent: None,
123 max_markup_percent: None,
124 applicable_transaction_types: Vec::new(),
125 effective_date,
126 end_date: None,
127 fee_currency: Some(currency),
128 fixed_fee_amount: Some(fee_amount),
129 documentation_requirements: DocumentationLevel::Standard,
130 requires_annual_benchmarking: false,
131 }
132 }
133
134 pub fn is_active_on(&self, date: NaiveDate) -> bool {
136 date >= self.effective_date && self.end_date.map_or(true, |end| date <= end)
137 }
138
139 pub fn calculate_transfer_price(&self, cost: Decimal) -> Decimal {
141 match self.method {
142 TransferPricingMethod::CostPlus => {
143 cost * (Decimal::ONE + self.markup_percent / Decimal::from(100))
144 }
145 TransferPricingMethod::FixedFee => self.fixed_fee_amount.unwrap_or(Decimal::ZERO),
146 TransferPricingMethod::ResalePrice => {
147 cost / (Decimal::ONE - self.markup_percent / Decimal::from(100))
149 }
150 TransferPricingMethod::TransactionalNetMargin => {
151 cost * (Decimal::ONE + self.markup_percent / Decimal::from(100))
152 }
153 TransferPricingMethod::ProfitSplit => {
154 let industry_margin = Decimal::new(15, 2); let total_profit = cost * industry_margin;
159 let seller_share = total_profit * self.markup_percent / Decimal::from(100);
160 cost + seller_share
161 }
162 TransferPricingMethod::ComparableUncontrolled => {
163 cost * (Decimal::ONE + self.markup_percent / Decimal::from(100))
167 }
168 }
169 }
170
171 pub fn calculate_markup(&self, cost: Decimal) -> Decimal {
173 self.calculate_transfer_price(cost) - cost
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
179#[serde(rename_all = "snake_case")]
180pub enum DocumentationLevel {
181 Minimal,
183 #[default]
185 Standard,
186 Comprehensive,
188 CbCR,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct TransferPriceCalculation {
195 pub policy_id: String,
197 pub base_amount: Decimal,
199 pub transfer_price: Decimal,
201 pub markup_amount: Decimal,
203 pub effective_markup_percent: Decimal,
205 pub currency: String,
207 pub calculation_date: NaiveDate,
209 pub is_arms_length: bool,
211}
212
213impl TransferPriceCalculation {
214 pub fn new(
216 policy: &TransferPricingPolicy,
217 base_amount: Decimal,
218 currency: String,
219 calculation_date: NaiveDate,
220 ) -> Self {
221 let transfer_price = policy.calculate_transfer_price(base_amount);
222 let markup_amount = transfer_price - base_amount;
223 let effective_markup_percent = if base_amount != Decimal::ZERO {
224 (markup_amount / base_amount) * Decimal::from(100)
225 } else {
226 Decimal::ZERO
227 };
228
229 let is_arms_length = match (policy.min_markup_percent, policy.max_markup_percent) {
231 (Some(min), Some(max)) => {
232 effective_markup_percent >= min && effective_markup_percent <= max
233 }
234 _ => true, };
236
237 Self {
238 policy_id: policy.policy_id.clone(),
239 base_amount,
240 transfer_price,
241 markup_amount,
242 effective_markup_percent,
243 currency,
244 calculation_date,
245 is_arms_length,
246 }
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct ArmsLengthRange {
253 pub lower_quartile: Decimal,
255 pub median: Decimal,
257 pub upper_quartile: Decimal,
259 pub iqr: Decimal,
261 pub comparable_count: usize,
263 pub study_date: NaiveDate,
265 pub validity_months: u32,
267}
268
269impl ArmsLengthRange {
270 pub fn new(
272 lower_quartile: Decimal,
273 median: Decimal,
274 upper_quartile: Decimal,
275 comparable_count: usize,
276 study_date: NaiveDate,
277 ) -> Self {
278 Self {
279 lower_quartile,
280 median,
281 upper_quartile,
282 iqr: upper_quartile - lower_quartile,
283 comparable_count,
284 study_date,
285 validity_months: 36, }
287 }
288
289 pub fn is_within_range(&self, margin: Decimal) -> bool {
291 margin >= self.lower_quartile && margin <= self.upper_quartile
292 }
293
294 pub fn is_valid_on(&self, date: NaiveDate) -> bool {
296 let months_elapsed = (date.year() - self.study_date.year()) * 12
297 + (date.month() as i32 - self.study_date.month() as i32);
298 months_elapsed >= 0 && (months_elapsed as u32) <= self.validity_months
299 }
300
301 pub fn get_adjustment(&self, margin: Decimal) -> Option<Decimal> {
303 if margin < self.lower_quartile || margin > self.upper_quartile {
304 Some(self.median - margin)
305 } else {
306 None
307 }
308 }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct TransferPricingAdjustment {
314 pub adjustment_id: String,
316 pub policy_id: String,
318 pub seller_company: String,
320 pub buyer_company: String,
322 pub fiscal_year: i32,
324 pub original_amount: Decimal,
326 pub adjusted_amount: Decimal,
328 pub adjustment_amount: Decimal,
330 pub currency: String,
332 pub adjustment_reason: AdjustmentReason,
334 pub adjustment_date: NaiveDate,
336}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
340#[serde(rename_all = "snake_case")]
341pub enum AdjustmentReason {
342 YearEndTrueUp,
344 BenchmarkingUpdate,
346 TaxAuthorityAdjustment,
348 ApaPricing,
350 CompetentAuthority,
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use rust_decimal_macros::dec;
358
359 #[test]
360 fn test_cost_plus_calculation() {
361 let policy = TransferPricingPolicy::new_cost_plus(
362 "TP001".to_string(),
363 "Standard Cost Plus".to_string(),
364 dec!(5), NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
366 );
367
368 let cost = dec!(1000);
369 let transfer_price = policy.calculate_transfer_price(cost);
370 assert_eq!(transfer_price, dec!(1050));
371
372 let markup = policy.calculate_markup(cost);
373 assert_eq!(markup, dec!(50));
374 }
375
376 #[test]
377 fn test_fixed_fee_policy() {
378 let policy = TransferPricingPolicy::new_fixed_fee(
379 "TP002".to_string(),
380 "Management Fee".to_string(),
381 dec!(50000),
382 "USD".to_string(),
383 NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
384 );
385
386 assert_eq!(policy.calculate_transfer_price(dec!(0)), dec!(50000));
388 assert_eq!(policy.calculate_transfer_price(dec!(100000)), dec!(50000));
389 }
390
391 #[test]
392 fn test_transfer_price_calculation() {
393 let policy = TransferPricingPolicy::new_cost_plus(
394 "TP001".to_string(),
395 "Cost Plus 8%".to_string(),
396 dec!(8),
397 NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
398 );
399
400 let calc = TransferPriceCalculation::new(
401 &policy,
402 dec!(10000),
403 "USD".to_string(),
404 NaiveDate::from_ymd_opt(2022, 6, 15).unwrap(),
405 );
406
407 assert_eq!(calc.base_amount, dec!(10000));
408 assert_eq!(calc.transfer_price, dec!(10800));
409 assert_eq!(calc.markup_amount, dec!(800));
410 assert_eq!(calc.effective_markup_percent, dec!(8));
411 assert!(calc.is_arms_length);
412 }
413
414 #[test]
415 fn test_arms_length_range() {
416 let range = ArmsLengthRange::new(
417 dec!(3),
418 dec!(5),
419 dec!(8),
420 15,
421 NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
422 );
423
424 assert!(range.is_within_range(dec!(5)));
425 assert!(range.is_within_range(dec!(3)));
426 assert!(range.is_within_range(dec!(8)));
427 assert!(!range.is_within_range(dec!(2)));
428 assert!(!range.is_within_range(dec!(10)));
429
430 assert_eq!(range.get_adjustment(dec!(1)), Some(dec!(4))); assert_eq!(range.get_adjustment(dec!(5)), None); }
434
435 #[test]
436 fn test_policy_active_date() {
437 let mut policy = TransferPricingPolicy::new_cost_plus(
438 "TP001".to_string(),
439 "Test Policy".to_string(),
440 dec!(5),
441 NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
442 );
443 policy.end_date = Some(NaiveDate::from_ymd_opt(2023, 12, 31).unwrap());
444
445 assert!(policy.is_active_on(NaiveDate::from_ymd_opt(2022, 6, 15).unwrap()));
446 assert!(!policy.is_active_on(NaiveDate::from_ymd_opt(2021, 12, 31).unwrap()));
447 assert!(!policy.is_active_on(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()));
448 }
449}