1use chrono::NaiveDate;
4use datasynth_core::models::{
5 AssetAccountDetermination, AssetClass, AssetStatus, DepreciationMethod, FixedAsset,
6 FixedAssetPool,
7};
8use rand::prelude::*;
9use rand_chacha::ChaCha8Rng;
10use rust_decimal::Decimal;
11
12#[derive(Debug, Clone)]
14pub struct AssetGeneratorConfig {
15 pub asset_class_distribution: Vec<(AssetClass, f64)>,
17 pub depreciation_method_distribution: Vec<(DepreciationMethod, f64)>,
19 pub useful_life_by_class: Vec<(AssetClass, u32)>,
21 pub acquisition_cost_range: (Decimal, Decimal),
23 pub salvage_value_percent: f64,
25 pub fully_depreciated_rate: f64,
27 pub disposed_rate: f64,
29}
30
31impl Default for AssetGeneratorConfig {
32 fn default() -> Self {
33 Self {
34 asset_class_distribution: vec![
35 (AssetClass::Buildings, 0.05),
36 (AssetClass::Machinery, 0.25),
37 (AssetClass::Vehicles, 0.15),
38 (AssetClass::Furniture, 0.15),
39 (AssetClass::ItEquipment, 0.25),
40 (AssetClass::Software, 0.10),
41 (AssetClass::LeaseholdImprovements, 0.05),
42 ],
43 depreciation_method_distribution: vec![
44 (DepreciationMethod::StraightLine, 0.70),
45 (DepreciationMethod::DoubleDecliningBalance, 0.15),
46 (DepreciationMethod::Macrs, 0.10),
47 (DepreciationMethod::SumOfYearsDigits, 0.05),
48 ],
49 useful_life_by_class: vec![
50 (AssetClass::Buildings, 480), (AssetClass::BuildingImprovements, 180), (AssetClass::Machinery, 84), (AssetClass::Vehicles, 60), (AssetClass::Furniture, 84), (AssetClass::ItEquipment, 36), (AssetClass::Software, 36), (AssetClass::LeaseholdImprovements, 120), (AssetClass::Land, 0), (AssetClass::ConstructionInProgress, 0), ],
61 acquisition_cost_range: (Decimal::from(1_000), Decimal::from(500_000)),
62 salvage_value_percent: 0.05,
63 fully_depreciated_rate: 0.10,
64 disposed_rate: 0.02,
65 }
66 }
67}
68
69const ASSET_DESCRIPTIONS: &[(AssetClass, &[&str])] = &[
71 (
72 AssetClass::Buildings,
73 &[
74 "Corporate Office Building",
75 "Manufacturing Facility",
76 "Warehouse Complex",
77 "Distribution Center",
78 "Research Laboratory",
79 "Administrative Building",
80 ],
81 ),
82 (
83 AssetClass::Machinery,
84 &[
85 "Production Line Equipment",
86 "CNC Machining Center",
87 "Assembly Robot System",
88 "Industrial Press Machine",
89 "Packaging Equipment",
90 "Testing Equipment",
91 "Quality Control System",
92 "Material Handling System",
93 ],
94 ),
95 (
96 AssetClass::Vehicles,
97 &[
98 "Delivery Truck",
99 "Company Car",
100 "Forklift",
101 "Van Fleet Unit",
102 "Executive Vehicle",
103 "Service Vehicle",
104 "Cargo Truck",
105 "Utility Vehicle",
106 ],
107 ),
108 (
109 AssetClass::Furniture,
110 &[
111 "Office Workstation Set",
112 "Conference Room Furniture",
113 "Executive Desk Set",
114 "Reception Area Furniture",
115 "Cubicle System",
116 "Storage Cabinet Set",
117 "Meeting Room Table",
118 "Ergonomic Chair Set",
119 ],
120 ),
121 (
122 AssetClass::ItEquipment,
123 &[
124 "Server Rack System",
125 "Network Switch Array",
126 "Desktop Computer Set",
127 "Laptop Fleet",
128 "Storage Array",
129 "Backup System",
130 "Security System",
131 "Communication System",
132 ],
133 ),
134 (
135 AssetClass::Software,
136 &[
137 "ERP System License",
138 "CAD Software Suite",
139 "Database License",
140 "Office Suite License",
141 "Security Software",
142 "Development Tools",
143 "Analytics Platform",
144 "CRM System",
145 ],
146 ),
147 (
148 AssetClass::LeaseholdImprovements,
149 &[
150 "Office Build-out",
151 "HVAC Improvements",
152 "Electrical Upgrades",
153 "Floor Renovations",
154 "Lighting System",
155 "Security Improvements",
156 "Accessibility Upgrades",
157 "IT Infrastructure",
158 ],
159 ),
160];
161
162pub struct AssetGenerator {
164 rng: ChaCha8Rng,
165 seed: u64,
166 config: AssetGeneratorConfig,
167 asset_counter: usize,
168}
169
170impl AssetGenerator {
171 pub fn new(seed: u64) -> Self {
173 Self::with_config(seed, AssetGeneratorConfig::default())
174 }
175
176 pub fn with_config(seed: u64, config: AssetGeneratorConfig) -> Self {
178 Self {
179 rng: ChaCha8Rng::seed_from_u64(seed),
180 seed,
181 config,
182 asset_counter: 0,
183 }
184 }
185
186 pub fn generate_asset(
188 &mut self,
189 company_code: &str,
190 acquisition_date: NaiveDate,
191 ) -> FixedAsset {
192 self.asset_counter += 1;
193
194 let asset_id = format!("FA-{}-{:06}", company_code, self.asset_counter);
195 let asset_class = self.select_asset_class();
196 let description = self.select_description(&asset_class);
197
198 let mut asset = FixedAsset::new(
199 asset_id,
200 description.to_string(),
201 asset_class,
202 company_code,
203 acquisition_date,
204 self.generate_acquisition_cost(),
205 );
206
207 asset.depreciation_method = self.select_depreciation_method(&asset_class);
209 asset.useful_life_months = self.get_useful_life(&asset_class);
210 asset.salvage_value = (asset.acquisition_cost
211 * Decimal::from_f64_retain(self.config.salvage_value_percent)
212 .unwrap_or(Decimal::from_f64_retain(0.05).unwrap()))
213 .round_dp(2);
214
215 asset.account_determination = self.generate_account_determination(&asset_class);
217
218 asset.location = Some(format!("P{}", company_code));
220 asset.cost_center = Some(format!("CC-{}-ADMIN", company_code));
221
222 if matches!(
224 asset_class,
225 AssetClass::Machinery | AssetClass::Vehicles | AssetClass::ItEquipment
226 ) {
227 asset.serial_number = Some(self.generate_serial_number());
228 }
229
230 if self.rng.gen::<f64>() < self.config.disposed_rate {
232 let disposal_date =
233 acquisition_date + chrono::Duration::days(self.rng.gen_range(365..1825) as i64);
234 let (proceeds, _gain_loss) = self.generate_disposal_values(&asset);
235 asset.dispose(disposal_date, proceeds);
236 } else if self.rng.gen::<f64>() < self.config.fully_depreciated_rate {
237 asset.accumulated_depreciation = asset.acquisition_cost - asset.salvage_value;
238 asset.net_book_value = asset.salvage_value;
239 }
240
241 asset
242 }
243
244 pub fn generate_asset_of_class(
246 &mut self,
247 asset_class: AssetClass,
248 company_code: &str,
249 acquisition_date: NaiveDate,
250 ) -> FixedAsset {
251 self.asset_counter += 1;
252
253 let asset_id = format!("FA-{}-{:06}", company_code, self.asset_counter);
254 let description = self.select_description(&asset_class);
255
256 let mut asset = FixedAsset::new(
257 asset_id,
258 description.to_string(),
259 asset_class,
260 company_code,
261 acquisition_date,
262 self.generate_acquisition_cost_for_class(&asset_class),
263 );
264
265 asset.depreciation_method = self.select_depreciation_method(&asset_class);
266 asset.useful_life_months = self.get_useful_life(&asset_class);
267 asset.salvage_value = (asset.acquisition_cost
268 * Decimal::from_f64_retain(self.config.salvage_value_percent)
269 .unwrap_or(Decimal::from_f64_retain(0.05).unwrap()))
270 .round_dp(2);
271
272 asset.account_determination = self.generate_account_determination(&asset_class);
273 asset.location = Some(format!("P{}", company_code));
274 asset.cost_center = Some(format!("CC-{}-ADMIN", company_code));
275
276 if matches!(
277 asset_class,
278 AssetClass::Machinery | AssetClass::Vehicles | AssetClass::ItEquipment
279 ) {
280 asset.serial_number = Some(self.generate_serial_number());
281 }
282
283 asset
284 }
285
286 pub fn generate_aged_asset(
288 &mut self,
289 company_code: &str,
290 acquisition_date: NaiveDate,
291 as_of_date: NaiveDate,
292 ) -> FixedAsset {
293 let mut asset = self.generate_asset(company_code, acquisition_date);
294
295 let months_elapsed = ((as_of_date - acquisition_date).num_days() / 30) as u32;
297
298 for month_offset in 0..months_elapsed {
300 if asset.status == AssetStatus::Active {
301 let dep_date =
303 acquisition_date + chrono::Duration::days((month_offset as i64 + 1) * 30);
304 let depreciation = asset.calculate_monthly_depreciation(dep_date);
305 asset.apply_depreciation(depreciation);
306 }
307 }
308
309 asset
310 }
311
312 pub fn generate_asset_pool(
314 &mut self,
315 count: usize,
316 company_code: &str,
317 date_range: (NaiveDate, NaiveDate),
318 ) -> FixedAssetPool {
319 let mut pool = FixedAssetPool::new();
320
321 let (start_date, end_date) = date_range;
322 let days_range = (end_date - start_date).num_days() as u64;
323
324 for _ in 0..count {
325 let acquisition_date =
326 start_date + chrono::Duration::days(self.rng.gen_range(0..=days_range) as i64);
327 let asset = self.generate_asset(company_code, acquisition_date);
328 pool.add_asset(asset);
329 }
330
331 pool
332 }
333
334 pub fn generate_aged_asset_pool(
336 &mut self,
337 count: usize,
338 company_code: &str,
339 acquisition_date_range: (NaiveDate, NaiveDate),
340 as_of_date: NaiveDate,
341 ) -> FixedAssetPool {
342 let mut pool = FixedAssetPool::new();
343
344 let (start_date, end_date) = acquisition_date_range;
345 let days_range = (end_date - start_date).num_days() as u64;
346
347 for _ in 0..count {
348 let acquisition_date =
349 start_date + chrono::Duration::days(self.rng.gen_range(0..=days_range) as i64);
350 let asset = self.generate_aged_asset(company_code, acquisition_date, as_of_date);
351 pool.add_asset(asset);
352 }
353
354 pool
355 }
356
357 pub fn generate_diverse_pool(
359 &mut self,
360 count: usize,
361 company_code: &str,
362 date_range: (NaiveDate, NaiveDate),
363 ) -> FixedAssetPool {
364 let mut pool = FixedAssetPool::new();
365
366 let (start_date, end_date) = date_range;
367 let days_range = (end_date - start_date).num_days() as u64;
368
369 let class_counts = [
371 (AssetClass::Buildings, (count as f64 * 0.05) as usize),
372 (AssetClass::Machinery, (count as f64 * 0.25) as usize),
373 (AssetClass::Vehicles, (count as f64 * 0.15) as usize),
374 (AssetClass::Furniture, (count as f64 * 0.15) as usize),
375 (AssetClass::ItEquipment, (count as f64 * 0.25) as usize),
376 (AssetClass::Software, (count as f64 * 0.10) as usize),
377 (
378 AssetClass::LeaseholdImprovements,
379 (count as f64 * 0.05) as usize,
380 ),
381 ];
382
383 for (class, class_count) in class_counts {
384 for _ in 0..class_count {
385 let acquisition_date =
386 start_date + chrono::Duration::days(self.rng.gen_range(0..=days_range) as i64);
387 let asset = self.generate_asset_of_class(class, company_code, acquisition_date);
388 pool.add_asset(asset);
389 }
390 }
391
392 while pool.assets.len() < count {
394 let acquisition_date =
395 start_date + chrono::Duration::days(self.rng.gen_range(0..=days_range) as i64);
396 let asset = self.generate_asset(company_code, acquisition_date);
397 pool.add_asset(asset);
398 }
399
400 pool
401 }
402
403 fn select_asset_class(&mut self) -> AssetClass {
405 let roll: f64 = self.rng.gen();
406 let mut cumulative = 0.0;
407
408 for (class, prob) in &self.config.asset_class_distribution {
409 cumulative += prob;
410 if roll < cumulative {
411 return *class;
412 }
413 }
414
415 AssetClass::ItEquipment
416 }
417
418 fn select_depreciation_method(&mut self, asset_class: &AssetClass) -> DepreciationMethod {
420 if matches!(
422 asset_class,
423 AssetClass::Land | AssetClass::ConstructionInProgress
424 ) {
425 return DepreciationMethod::StraightLine; }
427
428 let roll: f64 = self.rng.gen();
429 let mut cumulative = 0.0;
430
431 for (method, prob) in &self.config.depreciation_method_distribution {
432 cumulative += prob;
433 if roll < cumulative {
434 return *method;
435 }
436 }
437
438 DepreciationMethod::StraightLine
439 }
440
441 fn get_useful_life(&self, asset_class: &AssetClass) -> u32 {
443 for (class, months) in &self.config.useful_life_by_class {
444 if class == asset_class {
445 return *months;
446 }
447 }
448 60 }
450
451 fn select_description(&mut self, asset_class: &AssetClass) -> &'static str {
453 for (class, descriptions) in ASSET_DESCRIPTIONS {
454 if class == asset_class {
455 let idx = self.rng.gen_range(0..descriptions.len());
456 return descriptions[idx];
457 }
458 }
459 "Fixed Asset"
460 }
461
462 fn generate_acquisition_cost(&mut self) -> Decimal {
464 let min = self.config.acquisition_cost_range.0;
465 let max = self.config.acquisition_cost_range.1;
466 let range = (max - min).to_string().parse::<f64>().unwrap_or(0.0);
467 let offset =
468 Decimal::from_f64_retain(self.rng.gen::<f64>() * range).unwrap_or(Decimal::ZERO);
469 (min + offset).round_dp(2)
470 }
471
472 fn generate_acquisition_cost_for_class(&mut self, asset_class: &AssetClass) -> Decimal {
474 let (min, max) = match asset_class {
475 AssetClass::Buildings => (Decimal::from(500_000), Decimal::from(10_000_000)),
476 AssetClass::BuildingImprovements => (Decimal::from(50_000), Decimal::from(500_000)),
477 AssetClass::Machinery | AssetClass::MachineryEquipment => {
478 (Decimal::from(50_000), Decimal::from(1_000_000))
479 }
480 AssetClass::Vehicles => (Decimal::from(20_000), Decimal::from(100_000)),
481 AssetClass::Furniture | AssetClass::FurnitureFixtures => {
482 (Decimal::from(1_000), Decimal::from(50_000))
483 }
484 AssetClass::ItEquipment | AssetClass::ComputerHardware => {
485 (Decimal::from(2_000), Decimal::from(200_000))
486 }
487 AssetClass::Software | AssetClass::Intangibles => {
488 (Decimal::from(5_000), Decimal::from(500_000))
489 }
490 AssetClass::LeaseholdImprovements => (Decimal::from(10_000), Decimal::from(300_000)),
491 AssetClass::Land => (Decimal::from(100_000), Decimal::from(5_000_000)),
492 AssetClass::ConstructionInProgress => {
493 (Decimal::from(100_000), Decimal::from(2_000_000))
494 }
495 AssetClass::LowValueAssets => (Decimal::from(100), Decimal::from(5_000)),
496 };
497
498 let range = (max - min).to_string().parse::<f64>().unwrap_or(0.0);
499 let offset =
500 Decimal::from_f64_retain(self.rng.gen::<f64>() * range).unwrap_or(Decimal::ZERO);
501 (min + offset).round_dp(2)
502 }
503
504 fn generate_serial_number(&mut self) -> String {
506 format!(
507 "SN-{:04}-{:08}",
508 self.rng.gen_range(1000..9999),
509 self.rng.gen_range(10000000..99999999)
510 )
511 }
512
513 fn generate_disposal_values(&mut self, asset: &FixedAsset) -> (Decimal, Decimal) {
515 let proceeds_rate = self.rng.gen::<f64>() * 0.5;
517 let proceeds = (asset.acquisition_cost
518 * Decimal::from_f64_retain(proceeds_rate).unwrap_or(Decimal::ZERO))
519 .round_dp(2);
520
521 let nbv = asset.net_book_value;
523 let gain_loss = proceeds - nbv;
524
525 (proceeds, gain_loss)
526 }
527
528 fn generate_account_determination(
530 &self,
531 asset_class: &AssetClass,
532 ) -> AssetAccountDetermination {
533 match asset_class {
534 AssetClass::Buildings | AssetClass::BuildingImprovements => AssetAccountDetermination {
535 asset_account: "160000".to_string(),
536 accumulated_depreciation_account: "165000".to_string(),
537 depreciation_expense_account: "680000".to_string(),
538 gain_loss_account: "790000".to_string(),
539 gain_on_disposal_account: "790010".to_string(),
540 loss_on_disposal_account: "790020".to_string(),
541 acquisition_clearing_account: "199100".to_string(),
542 },
543 AssetClass::Machinery | AssetClass::MachineryEquipment => AssetAccountDetermination {
544 asset_account: "161000".to_string(),
545 accumulated_depreciation_account: "166000".to_string(),
546 depreciation_expense_account: "681000".to_string(),
547 gain_loss_account: "791000".to_string(),
548 gain_on_disposal_account: "791010".to_string(),
549 loss_on_disposal_account: "791020".to_string(),
550 acquisition_clearing_account: "199110".to_string(),
551 },
552 AssetClass::Vehicles => AssetAccountDetermination {
553 asset_account: "162000".to_string(),
554 accumulated_depreciation_account: "167000".to_string(),
555 depreciation_expense_account: "682000".to_string(),
556 gain_loss_account: "792000".to_string(),
557 gain_on_disposal_account: "792010".to_string(),
558 loss_on_disposal_account: "792020".to_string(),
559 acquisition_clearing_account: "199120".to_string(),
560 },
561 AssetClass::Furniture | AssetClass::FurnitureFixtures => AssetAccountDetermination {
562 asset_account: "163000".to_string(),
563 accumulated_depreciation_account: "168000".to_string(),
564 depreciation_expense_account: "683000".to_string(),
565 gain_loss_account: "793000".to_string(),
566 gain_on_disposal_account: "793010".to_string(),
567 loss_on_disposal_account: "793020".to_string(),
568 acquisition_clearing_account: "199130".to_string(),
569 },
570 AssetClass::ItEquipment | AssetClass::ComputerHardware => AssetAccountDetermination {
571 asset_account: "164000".to_string(),
572 accumulated_depreciation_account: "169000".to_string(),
573 depreciation_expense_account: "684000".to_string(),
574 gain_loss_account: "794000".to_string(),
575 gain_on_disposal_account: "794010".to_string(),
576 loss_on_disposal_account: "794020".to_string(),
577 acquisition_clearing_account: "199140".to_string(),
578 },
579 AssetClass::Software | AssetClass::Intangibles => AssetAccountDetermination {
580 asset_account: "170000".to_string(),
581 accumulated_depreciation_account: "175000".to_string(),
582 depreciation_expense_account: "685000".to_string(),
583 gain_loss_account: "795000".to_string(),
584 gain_on_disposal_account: "795010".to_string(),
585 loss_on_disposal_account: "795020".to_string(),
586 acquisition_clearing_account: "199150".to_string(),
587 },
588 AssetClass::LeaseholdImprovements => AssetAccountDetermination {
589 asset_account: "171000".to_string(),
590 accumulated_depreciation_account: "176000".to_string(),
591 depreciation_expense_account: "686000".to_string(),
592 gain_loss_account: "796000".to_string(),
593 gain_on_disposal_account: "796010".to_string(),
594 loss_on_disposal_account: "796020".to_string(),
595 acquisition_clearing_account: "199160".to_string(),
596 },
597 AssetClass::Land => {
598 AssetAccountDetermination {
599 asset_account: "150000".to_string(),
600 accumulated_depreciation_account: "".to_string(), depreciation_expense_account: "".to_string(),
602 gain_loss_account: "790000".to_string(),
603 gain_on_disposal_account: "790010".to_string(),
604 loss_on_disposal_account: "790020".to_string(),
605 acquisition_clearing_account: "199000".to_string(),
606 }
607 }
608 AssetClass::ConstructionInProgress => AssetAccountDetermination {
609 asset_account: "159000".to_string(),
610 accumulated_depreciation_account: "".to_string(),
611 depreciation_expense_account: "".to_string(),
612 gain_loss_account: "".to_string(),
613 gain_on_disposal_account: "".to_string(),
614 loss_on_disposal_account: "".to_string(),
615 acquisition_clearing_account: "199090".to_string(),
616 },
617 AssetClass::LowValueAssets => AssetAccountDetermination {
618 asset_account: "172000".to_string(),
619 accumulated_depreciation_account: "177000".to_string(),
620 depreciation_expense_account: "687000".to_string(),
621 gain_loss_account: "797000".to_string(),
622 gain_on_disposal_account: "797010".to_string(),
623 loss_on_disposal_account: "797020".to_string(),
624 acquisition_clearing_account: "199170".to_string(),
625 },
626 }
627 }
628
629 pub fn reset(&mut self) {
631 self.rng = ChaCha8Rng::seed_from_u64(self.seed);
632 self.asset_counter = 0;
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639
640 #[test]
641 fn test_asset_generation() {
642 let mut gen = AssetGenerator::new(42);
643 let asset = gen.generate_asset("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
644
645 assert!(!asset.asset_id.is_empty());
646 assert!(!asset.description.is_empty());
647 assert!(asset.acquisition_cost > Decimal::ZERO);
648 assert!(
649 asset.useful_life_months > 0
650 || matches!(
651 asset.asset_class,
652 AssetClass::Land | AssetClass::ConstructionInProgress
653 )
654 );
655 }
656
657 #[test]
658 fn test_asset_pool_generation() {
659 let mut gen = AssetGenerator::new(42);
660 let pool = gen.generate_asset_pool(
661 50,
662 "1000",
663 (
664 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
665 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
666 ),
667 );
668
669 assert_eq!(pool.assets.len(), 50);
670 }
671
672 #[test]
673 fn test_aged_asset() {
674 let mut gen = AssetGenerator::new(42);
675 let asset = gen.generate_aged_asset(
676 "1000",
677 NaiveDate::from_ymd_opt(2022, 1, 1).unwrap(),
678 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
679 );
680
681 if asset.status == AssetStatus::Active && asset.useful_life_months > 0 {
683 assert!(asset.accumulated_depreciation > Decimal::ZERO);
684 assert!(asset.net_book_value < asset.acquisition_cost);
685 }
686 }
687
688 #[test]
689 fn test_diverse_pool() {
690 let mut gen = AssetGenerator::new(42);
691 let pool = gen.generate_diverse_pool(
692 100,
693 "1000",
694 (
695 NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
696 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
697 ),
698 );
699
700 let machinery_count = pool
702 .assets
703 .iter()
704 .filter(|a| a.asset_class == AssetClass::Machinery)
705 .count();
706 let it_count = pool
707 .assets
708 .iter()
709 .filter(|a| a.asset_class == AssetClass::ItEquipment)
710 .count();
711
712 assert!(machinery_count > 0);
713 assert!(it_count > 0);
714 }
715
716 #[test]
717 fn test_deterministic_generation() {
718 let mut gen1 = AssetGenerator::new(42);
719 let mut gen2 = AssetGenerator::new(42);
720
721 let asset1 = gen1.generate_asset("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
722 let asset2 = gen2.generate_asset("1000", NaiveDate::from_ymd_opt(2024, 1, 1).unwrap());
723
724 assert_eq!(asset1.asset_id, asset2.asset_id);
725 assert_eq!(asset1.description, asset2.description);
726 assert_eq!(asset1.acquisition_cost, asset2.acquisition_cost);
727 }
728
729 #[test]
730 fn test_depreciation_calculation() {
731 let mut gen = AssetGenerator::new(42);
732 let mut asset = gen.generate_asset_of_class(
733 AssetClass::ItEquipment,
734 "1000",
735 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
736 );
737
738 let initial_nbv = asset.net_book_value;
739
740 let depreciation =
742 asset.calculate_monthly_depreciation(NaiveDate::from_ymd_opt(2024, 2, 1).unwrap());
743 asset.apply_depreciation(depreciation);
744
745 assert!(asset.accumulated_depreciation > Decimal::ZERO);
746 assert!(asset.net_book_value < initial_nbv);
747 }
748
749 #[test]
750 fn test_asset_class_cost_ranges() {
751 let mut gen = AssetGenerator::new(42);
752
753 let building = gen.generate_asset_of_class(
755 AssetClass::Buildings,
756 "1000",
757 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
758 );
759 let furniture = gen.generate_asset_of_class(
760 AssetClass::Furniture,
761 "1000",
762 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
763 );
764
765 assert!(building.acquisition_cost >= Decimal::from(500_000));
767 assert!(furniture.acquisition_cost <= Decimal::from(50_000));
768 }
769}