1use tracing::debug;
4
5use datasynth_core::accounts::{
6 asset_class_accounts, cash_accounts, control_accounts, dividend_accounts, dormant_accounts,
7 equity_accounts, expense_accounts, intangible_accounts, inventory_accounts, liability_accounts,
8 manufacturing_accounts, provision_accounts, revenue_accounts, suspense_accounts, tax_accounts,
9 treasury_accounts,
10};
11use datasynth_core::models::*;
12use datasynth_core::pcg_loader;
13use datasynth_core::skr_loader;
14use datasynth_core::traits::Generator;
15use datasynth_core::utils::seeded_rng;
16use rand_chacha::ChaCha8Rng;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum CoAFramework {
21 #[default]
23 UsGaap,
24 FrenchPcg,
26 GermanSkr04,
28}
29
30pub struct ChartOfAccountsGenerator {
32 rng: ChaCha8Rng,
33 seed: u64,
34 complexity: CoAComplexity,
35 industry: IndustrySector,
36 count: u64,
37 coa_framework: CoAFramework,
39 expand_industry_subaccounts: bool,
43}
44
45impl ChartOfAccountsGenerator {
46 pub fn new(complexity: CoAComplexity, industry: IndustrySector, seed: u64) -> Self {
48 Self {
49 rng: seeded_rng(seed, 0),
50 seed,
51 complexity,
52 industry,
53 count: 0,
54 coa_framework: CoAFramework::UsGaap,
55 expand_industry_subaccounts: false,
56 }
57 }
58
59 pub fn with_french_pcg(mut self, use_pcg: bool) -> Self {
63 if use_pcg {
64 self.coa_framework = CoAFramework::FrenchPcg;
65 }
66 self
67 }
68
69 pub fn with_expand_industry_subaccounts(mut self, expand: bool) -> Self {
76 self.expand_industry_subaccounts = expand;
77 self
78 }
79
80 pub fn with_coa_framework(mut self, framework: CoAFramework) -> Self {
82 self.coa_framework = framework;
83 self
84 }
85
86 pub fn generate(&mut self) -> ChartOfAccounts {
88 debug!(
89 complexity = ?self.complexity,
90 industry = ?self.industry,
91 seed = self.seed,
92 framework = ?self.coa_framework,
93 "Generating chart of accounts"
94 );
95
96 self.count += 1;
97 let mut coa = match self.coa_framework {
98 CoAFramework::UsGaap => self.generate_default(),
99 CoAFramework::FrenchPcg => self.generate_pcg(),
100 CoAFramework::GermanSkr04 => self.generate_skr(),
101 };
102
103 let framework_label = match self.coa_framework {
108 CoAFramework::UsGaap => "us_gaap",
109 CoAFramework::FrenchPcg => "french_pcg",
110 CoAFramework::GermanSkr04 => "german_skr04",
111 };
112 for account in coa.accounts.iter_mut() {
113 account.accounting_framework = Some(framework_label.to_string());
114 }
115 coa
116 }
117
118 fn generate_default(&mut self) -> ChartOfAccounts {
120 let target_count = self.complexity.target_count();
121 let mut coa = ChartOfAccounts::new(
122 format!("COA_{:?}_{}", self.industry, self.complexity.target_count()),
123 format!("{:?} Chart of Accounts", self.industry),
124 "US".to_string(),
125 self.industry,
126 self.complexity,
127 );
128
129 Self::seed_canonical_accounts(&mut coa);
131
132 if self.expand_industry_subaccounts {
133 self.generate_suspense_accounts(&mut coa);
141 Self::expand_with_industry_pack(&mut coa, self.industry);
142 } else {
143 self.generate_asset_accounts(&mut coa, target_count / 5);
145 self.generate_liability_accounts(&mut coa, target_count / 6);
146 self.generate_equity_accounts(&mut coa, target_count / 10);
147 self.generate_revenue_accounts(&mut coa, target_count / 5);
148 self.generate_expense_accounts(&mut coa, target_count / 4);
149 self.generate_suspense_accounts(&mut coa);
150 }
151
152 coa
153 }
154
155 fn expand_with_industry_pack(coa: &mut ChartOfAccounts, industry: IndustrySector) {
175 let pack = match datasynth_core::industry_packs::load_pack(industry) {
176 Ok(Some(p)) => p,
177 Ok(None) => return,
178 Err(e) => {
179 tracing::warn!(
180 "industry pack for {:?} failed to load: {} — skipping expansion",
181 industry,
182 e
183 );
184 return;
185 }
186 };
187
188 for expansion in &pack.expansions {
189 let parent_snapshot = match coa.get_account(&expansion.parent_account) {
192 Some(p) => p.clone(),
193 None => continue,
194 };
195
196 if let Some(parent_mut) = coa
198 .accounts
199 .iter_mut()
200 .find(|a| a.account_number == expansion.parent_account)
201 {
202 parent_mut.is_postable = false;
203 parent_mut.is_control_account = true;
204 }
205
206 for sub in &expansion.sub_accounts {
208 let sub_number = datasynth_core::industry_packs::render_sub_account_number(
209 &expansion.parent_account,
210 &sub.suffix,
211 );
212 if coa.get_account(&sub_number).is_some() {
216 continue;
217 }
218 let sub_name = datasynth_core::industry_packs::render_sub_account_name(
219 &expansion.parent_name,
220 &sub.name,
221 );
222 let mut sub_acct = GLAccount::new(
223 sub_number,
224 sub_name,
225 parent_snapshot.account_type,
226 parent_snapshot.sub_type,
227 );
228 sub_acct.account_class = parent_snapshot.account_class.clone();
230 sub_acct.account_class_name = parent_snapshot.account_class_name.clone();
231 sub_acct.account_sub_class = parent_snapshot.account_sub_class.clone();
232 sub_acct.account_sub_class_name = parent_snapshot.account_sub_class_name.clone();
233 sub_acct.account_group = parent_snapshot.account_group.clone();
234 sub_acct.parent_account = Some(parent_snapshot.account_number.clone());
235 sub_acct.hierarchy_level = parent_snapshot.hierarchy_level.saturating_add(1);
236 sub_acct.requires_cost_center = parent_snapshot.requires_cost_center;
237 sub_acct.requires_profit_center = parent_snapshot.requires_profit_center;
238 sub_acct.accounting_framework = parent_snapshot.accounting_framework.clone();
239 sub_acct.is_postable = true;
240 sub_acct.is_control_account = false;
241 coa.add_account(sub_acct);
242 }
243 }
244 }
245
246 fn generate_pcg(&mut self) -> ChartOfAccounts {
249 match pcg_loader::build_chart_of_accounts_from_pcg_2024(self.complexity, self.industry) {
250 Ok(coa) => coa,
251 Err(_) => self.generate_pcg_fallback(),
252 }
253 }
254
255 fn generate_skr(&mut self) -> ChartOfAccounts {
258 match skr_loader::build_chart_of_accounts_from_skr04(self.complexity, self.industry) {
259 Ok(coa) => coa,
260 Err(_) => self.generate_skr_fallback(),
261 }
262 }
263
264 fn generate_skr_fallback(&mut self) -> ChartOfAccounts {
266 use datasynth_core::skr;
267 let target_count = self.complexity.target_count();
268 let mut coa = ChartOfAccounts::new(
269 format!("COA_SKR04_{:?}_{}", self.industry, target_count),
270 format!("Standardkontenrahmen 04 – {:?}", self.industry),
271 "DE".to_string(),
272 self.industry,
273 self.complexity,
274 );
275 coa.account_format = "####".to_string();
276
277 let key_accounts = [
279 (
280 skr::control_accounts::AR_CONTROL,
281 "Forderungen aus L+L",
282 AccountType::Asset,
283 AccountSubType::AccountsReceivable,
284 ),
285 (
286 skr::control_accounts::AP_CONTROL,
287 "Verbindlichkeiten aus L+L",
288 AccountType::Liability,
289 AccountSubType::AccountsPayable,
290 ),
291 (
292 skr::control_accounts::INVENTORY,
293 "Vorräte",
294 AccountType::Asset,
295 AccountSubType::Inventory,
296 ),
297 (
298 skr::control_accounts::FIXED_ASSETS,
299 "Sachanlagen",
300 AccountType::Asset,
301 AccountSubType::FixedAssets,
302 ),
303 (
304 skr::cash_accounts::OPERATING_CASH,
305 "Bank",
306 AccountType::Asset,
307 AccountSubType::Cash,
308 ),
309 (
310 skr::cash_accounts::PETTY_CASH,
311 "Kasse",
312 AccountType::Asset,
313 AccountSubType::Cash,
314 ),
315 (
316 skr::equity_accounts::COMMON_STOCK,
317 "Gezeichnetes Kapital",
318 AccountType::Equity,
319 AccountSubType::CommonStock,
320 ),
321 (
322 skr::equity_accounts::RETAINED_EARNINGS,
323 "Gewinnvortrag",
324 AccountType::Equity,
325 AccountSubType::RetainedEarnings,
326 ),
327 (
328 skr::revenue_accounts::PRODUCT_REVENUE,
329 "Umsatzerlöse",
330 AccountType::Revenue,
331 AccountSubType::ProductRevenue,
332 ),
333 (
334 skr::revenue_accounts::SERVICE_REVENUE,
335 "Erlöse Leistungen",
336 AccountType::Revenue,
337 AccountSubType::ServiceRevenue,
338 ),
339 (
340 skr::expense_accounts::COGS,
341 "Materialaufwand",
342 AccountType::Expense,
343 AccountSubType::CostOfGoodsSold,
344 ),
345 (
346 skr::expense_accounts::SALARIES_WAGES,
347 "Löhne und Gehälter",
348 AccountType::Expense,
349 AccountSubType::OperatingExpenses,
350 ),
351 (
352 skr::expense_accounts::DEPRECIATION,
353 "Abschreibungen",
354 AccountType::Expense,
355 AccountSubType::DepreciationExpense,
356 ),
357 (
358 skr::expense_accounts::RENT,
359 "Miete",
360 AccountType::Expense,
361 AccountSubType::OperatingExpenses,
362 ),
363 ];
364
365 for (code, name, acc_type, sub_type) in key_accounts {
366 let mut account =
367 GLAccount::new(code.to_string(), name.to_string(), acc_type, sub_type);
368 account.requires_cost_center = acc_type == AccountType::Expense;
369 coa.add_account(account);
370 }
371
372 let mut num = 4100u32;
374 while coa.account_count() < target_count && num < 9900 {
375 let code = format!("{num:04}");
376 if coa.get_account(&code).is_none() {
377 let class = (num / 1000) as u8;
378 let (acc_type, sub_type) = match class {
379 0..=1 => (AccountType::Asset, AccountSubType::OtherAssets),
380 2 => (AccountType::Equity, AccountSubType::RetainedEarnings),
381 3 => (AccountType::Liability, AccountSubType::OtherLiabilities),
382 4 => (AccountType::Revenue, AccountSubType::OtherIncome),
383 5 => (AccountType::Expense, AccountSubType::CostOfGoodsSold),
384 6 => (AccountType::Expense, AccountSubType::OperatingExpenses),
385 7 => (AccountType::Expense, AccountSubType::InterestExpense),
386 _ => (AccountType::Asset, AccountSubType::SuspenseClearing),
387 };
388 coa.add_account(GLAccount::new(
389 code,
390 format!("Konto {num}"),
391 acc_type,
392 sub_type,
393 ));
394 }
395 num += 10;
396 }
397
398 coa
399 }
400
401 fn generate_pcg_fallback(&mut self) -> ChartOfAccounts {
403 let target_count = self.complexity.target_count();
404 let mut coa = ChartOfAccounts::new(
405 format!("COA_PCG_{:?}_{}", self.industry, target_count),
406 format!("Plan Comptable Général – {:?}", self.industry),
407 "FR".to_string(),
408 self.industry,
409 self.complexity,
410 );
411 coa.account_format = "######".to_string();
412
413 self.generate_pcg_class_1(&mut coa, target_count / 10);
414 self.generate_pcg_class_2(&mut coa, target_count / 6);
415 self.generate_pcg_class_3(&mut coa, target_count / 8);
416 self.generate_pcg_class_4(&mut coa, target_count / 5);
417 self.generate_pcg_class_5(&mut coa, target_count / 12);
418 self.generate_pcg_class_6(&mut coa, target_count / 4);
419 self.generate_pcg_class_7(&mut coa, target_count / 5);
420 self.generate_pcg_class_8(&mut coa);
421
422 coa
423 }
424
425 fn generate_pcg_class_1(&mut self, coa: &mut ChartOfAccounts, count: usize) {
426 let items = [
427 (
428 101,
429 "Capital",
430 AccountType::Equity,
431 AccountSubType::CommonStock,
432 ),
433 (
434 129,
435 "Résultat",
436 AccountType::Equity,
437 AccountSubType::RetainedEarnings,
438 ),
439 (
440 164,
441 "Emprunts",
442 AccountType::Liability,
443 AccountSubType::LongTermDebt,
444 ),
445 (
446 151,
447 "Provisions pour risques",
448 AccountType::Liability,
449 AccountSubType::AccruedLiabilities,
450 ),
451 ];
452 for (base, name, acc_type, sub_type) in items {
453 for i in 0..count.max(1) {
454 let num = base * 1000 + (i as u32 % 100);
455 coa.add_account(GLAccount::new(
456 format!("{num:06}"),
457 format!("{} {}", name, i + 1),
458 acc_type,
459 sub_type,
460 ));
461 }
462 }
463 }
464
465 fn generate_pcg_class_2(&mut self, coa: &mut ChartOfAccounts, count: usize) {
466 for i in 0..count.max(1) {
467 let num = 215000 + (i as u32 % 100);
468 coa.add_account(GLAccount::new(
469 format!("{num:06}"),
470 format!("Immobilisations {}", i + 1),
471 AccountType::Asset,
472 AccountSubType::FixedAssets,
473 ));
474 }
475 for i in 0..(count / 2).max(1) {
476 let num = 281000 + (i as u32 % 100);
477 coa.add_account(GLAccount::new(
478 format!("{num:06}"),
479 format!("Amortissements {}", i + 1),
480 AccountType::Asset,
481 AccountSubType::AccumulatedDepreciation,
482 ));
483 }
484 }
485
486 fn generate_pcg_class_3(&mut self, coa: &mut ChartOfAccounts, count: usize) {
487 for i in 0..count.max(1) {
488 let num = 310000 + (i as u32 % 1000);
489 coa.add_account(GLAccount::new(
490 format!("{num:06}"),
491 format!("Stocks {}", i + 1),
492 AccountType::Asset,
493 AccountSubType::Inventory,
494 ));
495 }
496 }
497
498 fn generate_pcg_class_4(&mut self, coa: &mut ChartOfAccounts, count: usize) {
499 for i in 0..count.max(1) {
500 let num = 411000 + (i as u32 % 1000);
501 coa.add_account(GLAccount::new(
502 format!("{num:06}"),
503 format!("Clients {}", i + 1),
504 AccountType::Asset,
505 AccountSubType::AccountsReceivable,
506 ));
507 }
508 for i in 0..count.max(1) {
509 let num = 401000 + (i as u32 % 1000);
510 coa.add_account(GLAccount::new(
511 format!("{num:06}"),
512 format!("Fournisseurs {}", i + 1),
513 AccountType::Liability,
514 AccountSubType::AccountsPayable,
515 ));
516 }
517 let clearing = GLAccount::new(
518 "408000".to_string(),
519 "Fournisseurs – non encore reçus".to_string(),
520 AccountType::Liability,
521 AccountSubType::GoodsReceivedClearing,
522 );
523 coa.add_account(clearing);
524 }
525
526 fn generate_pcg_class_5(&mut self, coa: &mut ChartOfAccounts, count: usize) {
527 let bases = [
528 (512, "Banque"),
529 (530, "Caisse"),
530 (511, "Valeurs à l'encaissement"),
531 ];
532 for (base, name) in bases {
533 for i in 0..(count / 3).max(1) {
534 let num = base * 1000 + (i as u32 % 100);
535 coa.add_account(GLAccount::new(
536 format!("{num:06}"),
537 format!("{} {}", name, i + 1),
538 AccountType::Asset,
539 AccountSubType::Cash,
540 ));
541 }
542 }
543 }
544
545 fn generate_pcg_class_6(&mut self, coa: &mut ChartOfAccounts, count: usize) {
546 let bases = [
547 (603, "Achats"),
548 (641, "Rémunérations"),
549 (681, "DAP"),
550 (613, "Loyers"),
551 (661, "Charges financières"),
552 ];
553 for (base, name) in bases {
554 for i in 0..(count / 5).max(1) {
555 let num = base * 1000 + (i as u32 % 100);
556 let mut account = GLAccount::new(
557 format!("{num:06}"),
558 format!("{} {}", name, i + 1),
559 AccountType::Expense,
560 AccountSubType::OperatingExpenses,
561 );
562 account.requires_cost_center = true;
563 coa.add_account(account);
564 }
565 }
566 }
567
568 fn generate_pcg_class_7(&mut self, coa: &mut ChartOfAccounts, count: usize) {
569 let bases = [
570 (701, "Ventes"),
571 (706, "Prestations"),
572 (758, "Produits divers"),
573 ];
574 for (base, name) in bases {
575 for i in 0..(count / 3).max(1) {
576 let num = base * 1000 + (i as u32 % 100);
577 coa.add_account(GLAccount::new(
578 format!("{num:06}"),
579 format!("{} {}", name, i + 1),
580 AccountType::Revenue,
581 AccountSubType::ProductRevenue,
582 ));
583 }
584 }
585 }
586
587 fn generate_pcg_class_8(&mut self, coa: &mut ChartOfAccounts) {
588 coa.add_account(GLAccount::new(
589 "808000".to_string(),
590 "Comptes spéciaux".to_string(),
591 AccountType::Asset,
592 AccountSubType::SuspenseClearing,
593 ));
594 }
595
596 fn seed_canonical_accounts(coa: &mut ChartOfAccounts) {
602 coa.add_account(GLAccount::new(
604 cash_accounts::OPERATING_CASH.to_string(),
605 "Operating Cash".to_string(),
606 AccountType::Asset,
607 AccountSubType::Cash,
608 ));
609 coa.add_account(GLAccount::new(
610 cash_accounts::BANK_ACCOUNT.to_string(),
611 "Bank Account".to_string(),
612 AccountType::Asset,
613 AccountSubType::Cash,
614 ));
615 coa.add_account(GLAccount::new(
616 cash_accounts::PETTY_CASH.to_string(),
617 "Petty Cash".to_string(),
618 AccountType::Asset,
619 AccountSubType::Cash,
620 ));
621 coa.add_account(GLAccount::new(
622 cash_accounts::WIRE_CLEARING.to_string(),
623 "Wire Transfer Clearing".to_string(),
624 AccountType::Asset,
625 AccountSubType::BankClearing,
626 ));
627
628 {
630 let mut acct = GLAccount::new(
631 control_accounts::AR_CONTROL.to_string(),
632 "Accounts Receivable Control".to_string(),
633 AccountType::Asset,
634 AccountSubType::AccountsReceivable,
635 );
636 acct.is_control_account = true;
637 coa.add_account(acct);
638 }
639 {
640 let mut acct = GLAccount::new(
641 control_accounts::IC_AR_CLEARING.to_string(),
642 "Intercompany AR Clearing".to_string(),
643 AccountType::Asset,
644 AccountSubType::AccountsReceivable,
645 );
646 acct.is_control_account = true;
647 coa.add_account(acct);
648 }
649 coa.add_account(GLAccount::new(
650 control_accounts::INVENTORY.to_string(),
651 "Inventory".to_string(),
652 AccountType::Asset,
653 AccountSubType::Inventory,
654 ));
655 coa.add_account(GLAccount::new(
656 control_accounts::FIXED_ASSETS.to_string(),
657 "Fixed Assets".to_string(),
658 AccountType::Asset,
659 AccountSubType::FixedAssets,
660 ));
661 coa.add_account(GLAccount::new(
662 control_accounts::ACCUMULATED_DEPRECIATION.to_string(),
663 "Accumulated Depreciation".to_string(),
664 AccountType::Asset,
665 AccountSubType::AccumulatedDepreciation,
666 ));
667
668 coa.add_account(GLAccount::new(
670 tax_accounts::INPUT_VAT.to_string(),
671 "Input VAT".to_string(),
672 AccountType::Asset,
673 AccountSubType::OtherReceivables,
674 ));
675 coa.add_account(GLAccount::new(
676 tax_accounts::DEFERRED_TAX_ASSET.to_string(),
677 "Deferred Tax Asset".to_string(),
678 AccountType::Asset,
679 AccountSubType::OtherAssets,
680 ));
681
682 {
684 let mut acct = GLAccount::new(
685 control_accounts::AP_CONTROL.to_string(),
686 "Accounts Payable Control".to_string(),
687 AccountType::Liability,
688 AccountSubType::AccountsPayable,
689 );
690 acct.is_control_account = true;
691 coa.add_account(acct);
692 }
693 {
694 let mut acct = GLAccount::new(
695 control_accounts::IC_AP_CLEARING.to_string(),
696 "Intercompany AP Clearing".to_string(),
697 AccountType::Liability,
698 AccountSubType::AccountsPayable,
699 );
700 acct.is_control_account = true;
701 coa.add_account(acct);
702 }
703 coa.add_account(GLAccount::new(
704 tax_accounts::SALES_TAX_PAYABLE.to_string(),
705 "Sales Tax Payable".to_string(),
706 AccountType::Liability,
707 AccountSubType::TaxLiabilities,
708 ));
709 coa.add_account(GLAccount::new(
710 tax_accounts::VAT_PAYABLE.to_string(),
711 "VAT Payable".to_string(),
712 AccountType::Liability,
713 AccountSubType::TaxLiabilities,
714 ));
715 coa.add_account(GLAccount::new(
716 tax_accounts::WITHHOLDING_TAX_PAYABLE.to_string(),
717 "Withholding Tax Payable".to_string(),
718 AccountType::Liability,
719 AccountSubType::TaxLiabilities,
720 ));
721 coa.add_account(GLAccount::new(
722 liability_accounts::ACCRUED_EXPENSES.to_string(),
723 "Accrued Expenses".to_string(),
724 AccountType::Liability,
725 AccountSubType::AccruedLiabilities,
726 ));
727 coa.add_account(GLAccount::new(
728 liability_accounts::ACCRUED_SALARIES.to_string(),
729 "Accrued Salaries".to_string(),
730 AccountType::Liability,
731 AccountSubType::AccruedLiabilities,
732 ));
733 coa.add_account(GLAccount::new(
734 liability_accounts::ACCRUED_BENEFITS.to_string(),
735 "Accrued Benefits".to_string(),
736 AccountType::Liability,
737 AccountSubType::AccruedLiabilities,
738 ));
739 coa.add_account(GLAccount::new(
740 liability_accounts::UNEARNED_REVENUE.to_string(),
741 "Unearned Revenue".to_string(),
742 AccountType::Liability,
743 AccountSubType::DeferredRevenue,
744 ));
745 coa.add_account(GLAccount::new(
746 liability_accounts::SHORT_TERM_DEBT.to_string(),
747 "Short-Term Debt".to_string(),
748 AccountType::Liability,
749 AccountSubType::ShortTermDebt,
750 ));
751 coa.add_account(GLAccount::new(
752 tax_accounts::DEFERRED_TAX_LIABILITY.to_string(),
753 "Deferred Tax Liability".to_string(),
754 AccountType::Liability,
755 AccountSubType::TaxLiabilities,
756 ));
757 coa.add_account(GLAccount::new(
758 liability_accounts::LONG_TERM_DEBT.to_string(),
759 "Long-Term Debt".to_string(),
760 AccountType::Liability,
761 AccountSubType::LongTermDebt,
762 ));
763 coa.add_account(GLAccount::new(
764 liability_accounts::IC_PAYABLE.to_string(),
765 "Intercompany Payable".to_string(),
766 AccountType::Liability,
767 AccountSubType::OtherLiabilities,
768 ));
769 {
770 let mut acct = GLAccount::new(
771 control_accounts::GR_IR_CLEARING.to_string(),
772 "GR/IR Clearing".to_string(),
773 AccountType::Liability,
774 AccountSubType::GoodsReceivedClearing,
775 );
776 acct.is_suspense_account = true;
777 coa.add_account(acct);
778 }
779
780 coa.add_account(GLAccount::new(
782 equity_accounts::COMMON_STOCK.to_string(),
783 "Common Stock".to_string(),
784 AccountType::Equity,
785 AccountSubType::CommonStock,
786 ));
787 coa.add_account(GLAccount::new(
788 equity_accounts::APIC.to_string(),
789 "Additional Paid-In Capital".to_string(),
790 AccountType::Equity,
791 AccountSubType::AdditionalPaidInCapital,
792 ));
793 coa.add_account(GLAccount::new(
794 equity_accounts::RETAINED_EARNINGS.to_string(),
795 "Retained Earnings".to_string(),
796 AccountType::Equity,
797 AccountSubType::RetainedEarnings,
798 ));
799 coa.add_account(GLAccount::new(
800 equity_accounts::CURRENT_YEAR_EARNINGS.to_string(),
801 "Current Year Earnings".to_string(),
802 AccountType::Equity,
803 AccountSubType::NetIncome,
804 ));
805 coa.add_account(GLAccount::new(
806 equity_accounts::TREASURY_STOCK.to_string(),
807 "Treasury Stock".to_string(),
808 AccountType::Equity,
809 AccountSubType::TreasuryStock,
810 ));
811 coa.add_account(GLAccount::new(
812 equity_accounts::CTA.to_string(),
813 "Currency Translation Adjustment".to_string(),
814 AccountType::Equity,
815 AccountSubType::OtherComprehensiveIncome,
816 ));
817
818 coa.add_account(GLAccount::new(
820 revenue_accounts::PRODUCT_REVENUE.to_string(),
821 "Product Revenue".to_string(),
822 AccountType::Revenue,
823 AccountSubType::ProductRevenue,
824 ));
825 coa.add_account(GLAccount::new(
826 revenue_accounts::SALES_DISCOUNTS.to_string(),
827 "Sales Discounts".to_string(),
828 AccountType::Revenue,
829 AccountSubType::ProductRevenue,
830 ));
831 coa.add_account(GLAccount::new(
832 revenue_accounts::SALES_RETURNS.to_string(),
833 "Sales Returns and Allowances".to_string(),
834 AccountType::Revenue,
835 AccountSubType::ProductRevenue,
836 ));
837 coa.add_account(GLAccount::new(
838 revenue_accounts::SERVICE_REVENUE.to_string(),
839 "Service Revenue".to_string(),
840 AccountType::Revenue,
841 AccountSubType::ServiceRevenue,
842 ));
843 coa.add_account(GLAccount::new(
844 revenue_accounts::IC_REVENUE.to_string(),
845 "Intercompany Revenue".to_string(),
846 AccountType::Revenue,
847 AccountSubType::OtherIncome,
848 ));
849 coa.add_account(GLAccount::new(
850 revenue_accounts::OTHER_REVENUE.to_string(),
851 "Other Revenue".to_string(),
852 AccountType::Revenue,
853 AccountSubType::OtherIncome,
854 ));
855
856 {
858 let mut acct = GLAccount::new(
859 expense_accounts::COGS.to_string(),
860 "Cost of Goods Sold".to_string(),
861 AccountType::Expense,
862 AccountSubType::CostOfGoodsSold,
863 );
864 acct.requires_cost_center = true;
865 coa.add_account(acct);
866 }
867 {
868 let mut acct = GLAccount::new(
869 expense_accounts::RAW_MATERIALS.to_string(),
870 "Raw Materials".to_string(),
871 AccountType::Expense,
872 AccountSubType::CostOfGoodsSold,
873 );
874 acct.requires_cost_center = true;
875 coa.add_account(acct);
876 }
877 {
878 let mut acct = GLAccount::new(
879 expense_accounts::DIRECT_LABOR.to_string(),
880 "Direct Labor".to_string(),
881 AccountType::Expense,
882 AccountSubType::CostOfGoodsSold,
883 );
884 acct.requires_cost_center = true;
885 coa.add_account(acct);
886 }
887 {
888 let mut acct = GLAccount::new(
889 expense_accounts::MANUFACTURING_OVERHEAD.to_string(),
890 "Manufacturing Overhead".to_string(),
891 AccountType::Expense,
892 AccountSubType::CostOfGoodsSold,
893 );
894 acct.requires_cost_center = true;
895 coa.add_account(acct);
896 }
897 {
898 let mut acct = GLAccount::new(
899 expense_accounts::DEPRECIATION.to_string(),
900 "Depreciation Expense".to_string(),
901 AccountType::Expense,
902 AccountSubType::DepreciationExpense,
903 );
904 acct.requires_cost_center = true;
905 coa.add_account(acct);
906 }
907 {
908 let mut acct = GLAccount::new(
909 expense_accounts::SALARIES_WAGES.to_string(),
910 "Salaries and Wages".to_string(),
911 AccountType::Expense,
912 AccountSubType::OperatingExpenses,
913 );
914 acct.requires_cost_center = true;
915 coa.add_account(acct);
916 }
917 {
918 let mut acct = GLAccount::new(
919 expense_accounts::BENEFITS.to_string(),
920 "Benefits Expense".to_string(),
921 AccountType::Expense,
922 AccountSubType::OperatingExpenses,
923 );
924 acct.requires_cost_center = true;
925 coa.add_account(acct);
926 }
927 {
928 let mut acct = GLAccount::new(
929 expense_accounts::RENT.to_string(),
930 "Rent Expense".to_string(),
931 AccountType::Expense,
932 AccountSubType::OperatingExpenses,
933 );
934 acct.requires_cost_center = true;
935 coa.add_account(acct);
936 }
937 {
938 let mut acct = GLAccount::new(
939 expense_accounts::UTILITIES.to_string(),
940 "Utilities Expense".to_string(),
941 AccountType::Expense,
942 AccountSubType::OperatingExpenses,
943 );
944 acct.requires_cost_center = true;
945 coa.add_account(acct);
946 }
947 {
948 let mut acct = GLAccount::new(
949 expense_accounts::OFFICE_SUPPLIES.to_string(),
950 "Office Supplies".to_string(),
951 AccountType::Expense,
952 AccountSubType::AdministrativeExpenses,
953 );
954 acct.requires_cost_center = true;
955 coa.add_account(acct);
956 }
957 {
958 let mut acct = GLAccount::new(
959 expense_accounts::TRAVEL_ENTERTAINMENT.to_string(),
960 "Travel and Entertainment".to_string(),
961 AccountType::Expense,
962 AccountSubType::SellingExpenses,
963 );
964 acct.requires_cost_center = true;
965 coa.add_account(acct);
966 }
967 {
968 let mut acct = GLAccount::new(
969 expense_accounts::PROFESSIONAL_FEES.to_string(),
970 "Professional Fees".to_string(),
971 AccountType::Expense,
972 AccountSubType::AdministrativeExpenses,
973 );
974 acct.requires_cost_center = true;
975 coa.add_account(acct);
976 }
977 {
978 let mut acct = GLAccount::new(
979 expense_accounts::INSURANCE.to_string(),
980 "Insurance Expense".to_string(),
981 AccountType::Expense,
982 AccountSubType::OperatingExpenses,
983 );
984 acct.requires_cost_center = true;
985 coa.add_account(acct);
986 }
987 {
988 let mut acct = GLAccount::new(
989 expense_accounts::BAD_DEBT.to_string(),
990 "Bad Debt Expense".to_string(),
991 AccountType::Expense,
992 AccountSubType::OperatingExpenses,
993 );
994 acct.requires_cost_center = true;
995 coa.add_account(acct);
996 }
997 {
998 let mut acct = GLAccount::new(
999 expense_accounts::INTEREST_EXPENSE.to_string(),
1000 "Interest Expense".to_string(),
1001 AccountType::Expense,
1002 AccountSubType::InterestExpense,
1003 );
1004 acct.requires_cost_center = true;
1005 coa.add_account(acct);
1006 }
1007 {
1008 let mut acct = GLAccount::new(
1009 expense_accounts::PURCHASE_DISCOUNTS.to_string(),
1010 "Purchase Discounts".to_string(),
1011 AccountType::Expense,
1012 AccountSubType::OtherExpenses,
1013 );
1014 acct.requires_cost_center = true;
1015 coa.add_account(acct);
1016 }
1017 {
1018 let mut acct = GLAccount::new(
1019 expense_accounts::FX_GAIN_LOSS.to_string(),
1020 "FX Gain/Loss".to_string(),
1021 AccountType::Expense,
1022 AccountSubType::ForeignExchangeLoss,
1023 );
1024 acct.requires_cost_center = true;
1025 coa.add_account(acct);
1026 }
1027
1028 {
1030 let mut acct = GLAccount::new(
1031 tax_accounts::TAX_EXPENSE.to_string(),
1032 "Tax Expense".to_string(),
1033 AccountType::Expense,
1034 AccountSubType::TaxExpense,
1035 );
1036 acct.requires_cost_center = true;
1037 coa.add_account(acct);
1038 }
1039
1040 Self::seed_asset_class_accounts(coa);
1042
1043 Self::seed_manufacturing_accounts(coa);
1045
1046 Self::seed_intangible_accounts(coa);
1048
1049 Self::seed_treasury_accounts(coa);
1051
1052 Self::seed_provision_accounts(coa);
1054
1055 Self::seed_dividend_accounts(coa);
1057
1058 Self::seed_compensation_accounts(coa);
1060
1061 Self::seed_inventory_subledger_accounts(coa);
1063
1064 Self::seed_additional_tax_accounts(coa);
1066
1067 Self::seed_additional_equity_accounts(coa);
1069
1070 Self::seed_dormant_accounts(coa);
1072
1073 {
1075 let mut acct = GLAccount::new(
1076 suspense_accounts::GENERAL_SUSPENSE.to_string(),
1077 "General Suspense".to_string(),
1078 AccountType::Asset,
1079 AccountSubType::SuspenseClearing,
1080 );
1081 acct.is_suspense_account = true;
1082 coa.add_account(acct);
1083 }
1084 {
1085 let mut acct = GLAccount::new(
1086 suspense_accounts::PAYROLL_CLEARING.to_string(),
1087 "Payroll Clearing".to_string(),
1088 AccountType::Asset,
1089 AccountSubType::SuspenseClearing,
1090 );
1091 acct.is_suspense_account = true;
1092 coa.add_account(acct);
1093 }
1094 {
1095 let mut acct = GLAccount::new(
1096 suspense_accounts::BANK_RECONCILIATION_SUSPENSE.to_string(),
1097 "Bank Reconciliation Suspense".to_string(),
1098 AccountType::Asset,
1099 AccountSubType::BankClearing,
1100 );
1101 acct.is_suspense_account = true;
1102 coa.add_account(acct);
1103 }
1104 {
1105 let mut acct = GLAccount::new(
1106 suspense_accounts::IC_ELIMINATION_SUSPENSE.to_string(),
1107 "IC Elimination Suspense".to_string(),
1108 AccountType::Asset,
1109 AccountSubType::IntercompanyClearing,
1110 );
1111 acct.is_suspense_account = true;
1112 coa.add_account(acct);
1113 }
1114 }
1115
1116 fn seed_asset_class_accounts(coa: &mut ChartOfAccounts) {
1120 let acquisitions = [
1121 (asset_class_accounts::LAND, "Land"),
1122 (asset_class_accounts::BUILDINGS, "Buildings"),
1123 (
1124 asset_class_accounts::BUILDING_IMPROVEMENTS,
1125 "Building Improvements",
1126 ),
1127 (
1128 asset_class_accounts::MACHINERY_EQUIPMENT,
1129 "Machinery & Equipment",
1130 ),
1131 (asset_class_accounts::VEHICLES, "Vehicles"),
1132 (asset_class_accounts::OFFICE_EQUIPMENT, "Office Equipment"),
1133 (asset_class_accounts::COMPUTER_HARDWARE, "Computer Hardware"),
1134 (
1135 asset_class_accounts::SOFTWARE_INTANGIBLES,
1136 "Software / Intangibles",
1137 ),
1138 (
1139 asset_class_accounts::FURNITURE_FIXTURES,
1140 "Furniture & Fixtures",
1141 ),
1142 (
1143 asset_class_accounts::LEASEHOLD_IMPROVEMENTS,
1144 "Leasehold Improvements",
1145 ),
1146 (asset_class_accounts::OTHER_ASSETS, "Other Fixed Assets"),
1147 (asset_class_accounts::LOW_VALUE_ASSETS, "Low-Value Assets"),
1148 (
1149 asset_class_accounts::CONSTRUCTION_IN_PROGRESS,
1150 "Construction in Progress",
1151 ),
1152 ];
1153 for (number, name) in acquisitions {
1154 coa.add_account(GLAccount::new(
1155 number.to_string(),
1156 name.to_string(),
1157 AccountType::Asset,
1158 AccountSubType::FixedAssets,
1159 ));
1160 }
1161
1162 let depreciation_contras = [
1163 (asset_class_accounts::ACC_DEP_LAND, "Acc. Dep. — Land"),
1164 (
1165 asset_class_accounts::ACC_DEP_BUILDINGS,
1166 "Acc. Dep. — Buildings",
1167 ),
1168 (
1169 asset_class_accounts::ACC_DEP_MACHINERY,
1170 "Acc. Dep. — Machinery",
1171 ),
1172 (
1173 asset_class_accounts::ACC_DEP_VEHICLES,
1174 "Acc. Dep. — Vehicles",
1175 ),
1176 (
1177 asset_class_accounts::ACC_DEP_OFFICE_EQUIPMENT,
1178 "Acc. Dep. — Office Equipment",
1179 ),
1180 (
1181 asset_class_accounts::ACC_DEP_SOFTWARE,
1182 "Acc. Dep. — Software / Intangibles",
1183 ),
1184 (
1185 asset_class_accounts::ACC_DEP_FURNITURE,
1186 "Acc. Dep. — Furniture",
1187 ),
1188 (
1189 asset_class_accounts::ACC_DEP_LEASEHOLD,
1190 "Acc. Dep. — Leasehold Improvements",
1191 ),
1192 (asset_class_accounts::ACC_DEP_OTHER, "Acc. Dep. — Other"),
1193 (asset_class_accounts::ACC_DEP_CIP, "Acc. Dep. — CIP"),
1194 ];
1195 for (number, name) in depreciation_contras {
1196 coa.add_account(GLAccount::new(
1197 number.to_string(),
1198 name.to_string(),
1199 AccountType::Asset,
1200 AccountSubType::AccumulatedDepreciation,
1201 ));
1202 }
1203
1204 {
1207 let mut acct = GLAccount::new(
1208 asset_class_accounts::DEPRECIATION_EXPENSE.to_string(),
1209 "FA Depreciation Expense".to_string(),
1210 AccountType::Expense,
1211 AccountSubType::DepreciationExpense,
1212 );
1213 acct.requires_cost_center = true;
1214 coa.add_account(acct);
1215 }
1216 coa.add_account(GLAccount::new(
1217 asset_class_accounts::GAIN_ON_DISPOSAL.to_string(),
1218 "Gain on Disposal of Fixed Assets".to_string(),
1219 AccountType::Revenue,
1220 AccountSubType::GainOnSale,
1221 ));
1222 coa.add_account(GLAccount::new(
1223 asset_class_accounts::LOSS_ON_DISPOSAL.to_string(),
1224 "Loss on Disposal of Fixed Assets".to_string(),
1225 AccountType::Expense,
1226 AccountSubType::LossOnSale,
1227 ));
1228 }
1229
1230 fn seed_manufacturing_accounts(coa: &mut ChartOfAccounts) {
1232 coa.add_account(GLAccount::new(
1233 manufacturing_accounts::FINISHED_GOODS.to_string(),
1234 "Finished Goods".to_string(),
1235 AccountType::Asset,
1236 AccountSubType::Inventory,
1237 ));
1238 coa.add_account(GLAccount::new(
1239 manufacturing_accounts::WIP.to_string(),
1240 "Work in Process".to_string(),
1241 AccountType::Asset,
1242 AccountSubType::Inventory,
1243 ));
1244 coa.add_account(GLAccount::new(
1245 manufacturing_accounts::LABOR_ACCRUAL.to_string(),
1246 "Labor Accrual".to_string(),
1247 AccountType::Liability,
1248 AccountSubType::AccruedLiabilities,
1249 ));
1250 coa.add_account(GLAccount::new(
1251 manufacturing_accounts::WARRANTY_PROVISION.to_string(),
1252 "Warranty Provision".to_string(),
1253 AccountType::Liability,
1254 AccountSubType::OtherLiabilities,
1255 ));
1256
1257 let variance_accounts = [
1258 (
1259 manufacturing_accounts::SCRAP_EXPENSE,
1260 "Scrap Expense",
1261 AccountSubType::CostOfGoodsSold,
1262 ),
1263 (
1264 manufacturing_accounts::OVERHEAD_APPLIED,
1265 "Overhead Applied",
1266 AccountSubType::CostOfGoodsSold,
1267 ),
1268 (
1269 manufacturing_accounts::MATERIAL_PRICE_VARIANCE,
1270 "Material Price Variance",
1271 AccountSubType::CostOfGoodsSold,
1272 ),
1273 (
1274 manufacturing_accounts::MATERIAL_USAGE_VARIANCE,
1275 "Material Usage Variance",
1276 AccountSubType::CostOfGoodsSold,
1277 ),
1278 (
1279 manufacturing_accounts::LABOR_RATE_VARIANCE,
1280 "Labor Rate Variance",
1281 AccountSubType::CostOfGoodsSold,
1282 ),
1283 (
1284 manufacturing_accounts::LABOR_EFFICIENCY_VARIANCE,
1285 "Labor Efficiency Variance",
1286 AccountSubType::CostOfGoodsSold,
1287 ),
1288 (
1289 manufacturing_accounts::OVERHEAD_VOLUME_VARIANCE,
1290 "Overhead Volume Variance",
1291 AccountSubType::CostOfGoodsSold,
1292 ),
1293 (
1294 manufacturing_accounts::WARRANTY_EXPENSE,
1295 "Warranty Expense",
1296 AccountSubType::OperatingExpenses,
1297 ),
1298 ];
1299 for (number, name, sub) in variance_accounts {
1300 let mut acct = GLAccount::new(
1301 number.to_string(),
1302 name.to_string(),
1303 AccountType::Expense,
1304 sub,
1305 );
1306 acct.requires_cost_center = true;
1307 coa.add_account(acct);
1308 }
1309 }
1310
1311 fn seed_intangible_accounts(coa: &mut ChartOfAccounts) {
1313 let intangibles = [
1314 (
1315 intangible_accounts::GOODWILL,
1316 "Goodwill",
1317 AccountType::Asset,
1318 AccountSubType::IntangibleAssets,
1319 ),
1320 (
1321 intangible_accounts::CUSTOMER_RELATIONSHIPS,
1322 "Customer Relationships",
1323 AccountType::Asset,
1324 AccountSubType::IntangibleAssets,
1325 ),
1326 (
1327 intangible_accounts::TRADE_NAME,
1328 "Trade Name / Brand",
1329 AccountType::Asset,
1330 AccountSubType::IntangibleAssets,
1331 ),
1332 (
1333 intangible_accounts::TECHNOLOGY,
1334 "Technology / Developed Software",
1335 AccountType::Asset,
1336 AccountSubType::IntangibleAssets,
1337 ),
1338 (
1339 intangible_accounts::ACCUMULATED_AMORTIZATION,
1340 "Accumulated Amortization",
1341 AccountType::Asset,
1342 AccountSubType::AccumulatedDepreciation,
1343 ),
1344 (
1345 intangible_accounts::AMORTIZATION_EXPENSE,
1346 "Amortization Expense — Intangibles",
1347 AccountType::Expense,
1348 AccountSubType::AmortizationExpense,
1349 ),
1350 (
1351 intangible_accounts::BARGAIN_PURCHASE_GAIN,
1352 "Bargain Purchase Gain",
1353 AccountType::Revenue,
1354 AccountSubType::OtherIncome,
1355 ),
1356 ];
1357 for (number, name, ty, sub) in intangibles {
1358 coa.add_account(GLAccount::new(
1359 number.to_string(),
1360 name.to_string(),
1361 ty,
1362 sub,
1363 ));
1364 }
1365 }
1366
1367 fn seed_treasury_accounts(coa: &mut ChartOfAccounts) {
1369 let entries = [
1370 (
1371 treasury_accounts::INTEREST_PAYABLE,
1372 "Interest Payable",
1373 AccountType::Liability,
1374 AccountSubType::AccruedLiabilities,
1375 ),
1376 (
1377 treasury_accounts::DEBT_PREMIUM,
1378 "Debt Premium",
1379 AccountType::Liability,
1380 AccountSubType::LongTermDebt,
1381 ),
1382 (
1383 treasury_accounts::DEBT_DISCOUNT,
1384 "Debt Discount",
1385 AccountType::Liability,
1386 AccountSubType::LongTermDebt,
1387 ),
1388 (
1389 treasury_accounts::DERIVATIVE_ASSET,
1390 "Derivative Asset",
1391 AccountType::Asset,
1392 AccountSubType::OtherAssets,
1393 ),
1394 (
1395 treasury_accounts::DERIVATIVE_LIABILITY,
1396 "Derivative Liability",
1397 AccountType::Liability,
1398 AccountSubType::OtherLiabilities,
1399 ),
1400 (
1401 treasury_accounts::OCI_CASH_FLOW_HEDGE,
1402 "OCI — Cash Flow Hedge Reserve",
1403 AccountType::Equity,
1404 AccountSubType::OtherComprehensiveIncome,
1405 ),
1406 (
1407 treasury_accounts::HEDGE_INEFFECTIVENESS,
1408 "Hedge Ineffectiveness",
1409 AccountType::Expense,
1410 AccountSubType::OtherExpenses,
1411 ),
1412 (
1413 treasury_accounts::CASH_POOL_IC_RECEIVABLE,
1414 "IC Receivable — Cash Pool",
1415 AccountType::Asset,
1416 AccountSubType::AccountsReceivable,
1417 ),
1418 (
1419 treasury_accounts::CASH_POOL_IC_PAYABLE,
1420 "IC Payable — Cash Pool",
1421 AccountType::Liability,
1422 AccountSubType::AccountsPayable,
1423 ),
1424 ];
1425 for (number, name, ty, sub) in entries {
1426 coa.add_account(GLAccount::new(
1427 number.to_string(),
1428 name.to_string(),
1429 ty,
1430 sub,
1431 ));
1432 }
1433 }
1434
1435 fn seed_provision_accounts(coa: &mut ChartOfAccounts) {
1437 coa.add_account(GLAccount::new(
1438 provision_accounts::PROVISION_LIABILITY.to_string(),
1439 "Provision Liability".to_string(),
1440 AccountType::Liability,
1441 AccountSubType::OtherLiabilities,
1442 ));
1443 let mut prov_exp = GLAccount::new(
1444 provision_accounts::PROVISION_EXPENSE.to_string(),
1445 "Provision Expense".to_string(),
1446 AccountType::Expense,
1447 AccountSubType::OperatingExpenses,
1448 );
1449 prov_exp.requires_cost_center = true;
1450 coa.add_account(prov_exp);
1451 }
1452
1453 fn seed_dividend_accounts(coa: &mut ChartOfAccounts) {
1455 coa.add_account(GLAccount::new(
1456 dividend_accounts::DIVIDENDS_PAYABLE.to_string(),
1457 "Dividends Payable".to_string(),
1458 AccountType::Liability,
1459 AccountSubType::OtherLiabilities,
1460 ));
1461 coa.add_account(GLAccount::new(
1462 dividend_accounts::DIVIDENDS_DECLARED.to_string(),
1463 "Dividends Declared".to_string(),
1464 AccountType::Equity,
1465 AccountSubType::RetainedEarnings,
1466 ));
1467 }
1468
1469 fn seed_compensation_accounts(coa: &mut ChartOfAccounts) {
1471 coa.add_account(GLAccount::new(
1472 liability_accounts::NET_PENSION_LIABILITY.to_string(),
1473 "Net Pension Liability".to_string(),
1474 AccountType::Liability,
1475 AccountSubType::PensionLiabilities,
1476 ));
1477 coa.add_account(GLAccount::new(
1478 equity_accounts::OCI_REMEASUREMENTS.to_string(),
1479 "OCI — Pension Remeasurements".to_string(),
1480 AccountType::Equity,
1481 AccountSubType::OtherComprehensiveIncome,
1482 ));
1483 coa.add_account(GLAccount::new(
1484 equity_accounts::APIC_STOCK_COMP.to_string(),
1485 "APIC — Stock Compensation".to_string(),
1486 AccountType::Equity,
1487 AccountSubType::AdditionalPaidInCapital,
1488 ));
1489 let mut pension_exp = GLAccount::new(
1490 expense_accounts::PENSION_EXPENSE.to_string(),
1491 "Pension Expense".to_string(),
1492 AccountType::Expense,
1493 AccountSubType::OperatingExpenses,
1494 );
1495 pension_exp.requires_cost_center = true;
1496 coa.add_account(pension_exp);
1497 let mut stock_comp = GLAccount::new(
1498 expense_accounts::STOCK_COMP_EXPENSE.to_string(),
1499 "Stock-Based Compensation Expense".to_string(),
1500 AccountType::Expense,
1501 AccountSubType::OperatingExpenses,
1502 );
1503 stock_comp.requires_cost_center = true;
1504 coa.add_account(stock_comp);
1505 }
1506
1507 fn seed_inventory_subledger_accounts(coa: &mut ChartOfAccounts) {
1509 coa.add_account(GLAccount::new(
1510 inventory_accounts::WRITEUP_INCOME.to_string(),
1511 "Inventory Write-up Income".to_string(),
1512 AccountType::Revenue,
1513 AccountSubType::OtherIncome,
1514 ));
1515 let mut writedown = GLAccount::new(
1516 inventory_accounts::WRITEDOWN_EXPENSE.to_string(),
1517 "Inventory Write-down Expense".to_string(),
1518 AccountType::Expense,
1519 AccountSubType::CostOfGoodsSold,
1520 );
1521 writedown.requires_cost_center = true;
1522 coa.add_account(writedown);
1523 }
1524
1525 fn seed_additional_tax_accounts(coa: &mut ChartOfAccounts) {
1527 coa.add_account(GLAccount::new(
1528 tax_accounts::INCOME_TAX_PAYABLE.to_string(),
1529 "Income Tax Payable".to_string(),
1530 AccountType::Liability,
1531 AccountSubType::TaxLiabilities,
1532 ));
1533 coa.add_account(GLAccount::new(
1534 tax_accounts::TAX_RECEIVABLE.to_string(),
1535 "Tax Receivable".to_string(),
1536 AccountType::Asset,
1537 AccountSubType::OtherReceivables,
1538 ));
1539 }
1540
1541 fn seed_dormant_accounts(coa: &mut ChartOfAccounts) {
1553 let entries = [
1554 (
1555 dormant_accounts::LEGACY_SUSPENSE,
1556 "Legacy Suspense (migrated)",
1557 AccountType::Asset,
1558 AccountSubType::SuspenseClearing,
1559 ),
1560 (
1561 dormant_accounts::LEGACY_CLEARING,
1562 "Legacy Clearing (predecessor system)",
1563 AccountType::Liability,
1564 AccountSubType::OtherLiabilities,
1565 ),
1566 (
1567 dormant_accounts::OBSOLETE,
1568 "Obsolete Account",
1569 AccountType::Equity,
1570 AccountSubType::OtherComprehensiveIncome,
1571 ),
1572 (
1573 dormant_accounts::TEST_ACCOUNT,
1574 "Test Account (QA residue)",
1575 AccountType::Asset,
1576 AccountSubType::SuspenseClearing,
1577 ),
1578 ];
1579 for (number, name, ty, sub) in entries {
1580 let mut acct = GLAccount::new(number.to_string(), name.to_string(), ty, sub);
1581 acct.is_blocked = true;
1582 acct.is_postable = false;
1583 acct.is_suspense_account = matches!(sub, AccountSubType::SuspenseClearing);
1584 coa.add_account(acct);
1585 }
1586 }
1587
1588 fn seed_additional_equity_accounts(coa: &mut ChartOfAccounts) {
1590 coa.add_account(GLAccount::new(
1591 equity_accounts::INCOME_SUMMARY.to_string(),
1592 "Income Summary".to_string(),
1593 AccountType::Equity,
1594 AccountSubType::NetIncome,
1595 ));
1596 coa.add_account(GLAccount::new(
1597 equity_accounts::DIVIDENDS_PAID.to_string(),
1598 "Dividends Paid".to_string(),
1599 AccountType::Equity,
1600 AccountSubType::RetainedEarnings,
1601 ));
1602 }
1603
1604 fn generate_asset_accounts(&mut self, coa: &mut ChartOfAccounts, count: usize) {
1605 let sub_types = vec![
1606 (AccountSubType::Cash, "Cash", 0.15),
1607 (
1608 AccountSubType::AccountsReceivable,
1609 "Accounts Receivable",
1610 0.20,
1611 ),
1612 (AccountSubType::Inventory, "Inventory", 0.15),
1613 (AccountSubType::PrepaidExpenses, "Prepaid Expenses", 0.10),
1614 (AccountSubType::FixedAssets, "Fixed Assets", 0.25),
1615 (
1616 AccountSubType::AccumulatedDepreciation,
1617 "Accumulated Depreciation",
1618 0.10,
1619 ),
1620 (AccountSubType::OtherAssets, "Other Assets", 0.05),
1621 ];
1622
1623 let mut account_num = 100000u32;
1624 for (sub_type, name_prefix, weight) in sub_types {
1625 let sub_count = ((count as f64) * weight).round() as usize;
1626 for i in 0..sub_count.max(1) {
1627 let account = GLAccount::new(
1628 format!("{account_num}"),
1629 format!("{} {}", name_prefix, i + 1),
1630 AccountType::Asset,
1631 sub_type,
1632 );
1633 coa.add_account(account);
1634 account_num += 10;
1635 }
1636 }
1637 }
1638
1639 fn generate_liability_accounts(&mut self, coa: &mut ChartOfAccounts, count: usize) {
1640 let sub_types = vec![
1641 (AccountSubType::AccountsPayable, "Accounts Payable", 0.25),
1642 (
1643 AccountSubType::AccruedLiabilities,
1644 "Accrued Liabilities",
1645 0.20,
1646 ),
1647 (AccountSubType::ShortTermDebt, "Short-Term Debt", 0.15),
1648 (AccountSubType::LongTermDebt, "Long-Term Debt", 0.15),
1649 (AccountSubType::DeferredRevenue, "Deferred Revenue", 0.15),
1650 (AccountSubType::TaxLiabilities, "Tax Liabilities", 0.10),
1651 ];
1652
1653 let mut account_num = 200000u32;
1654 for (sub_type, name_prefix, weight) in sub_types {
1655 let sub_count = ((count as f64) * weight).round() as usize;
1656 for i in 0..sub_count.max(1) {
1657 let account = GLAccount::new(
1658 format!("{account_num}"),
1659 format!("{} {}", name_prefix, i + 1),
1660 AccountType::Liability,
1661 sub_type,
1662 );
1663 coa.add_account(account);
1664 account_num += 10;
1665 }
1666 }
1667 }
1668
1669 fn generate_equity_accounts(&mut self, coa: &mut ChartOfAccounts, count: usize) {
1670 let sub_types = vec![
1671 (AccountSubType::CommonStock, "Common Stock", 0.20),
1672 (AccountSubType::RetainedEarnings, "Retained Earnings", 0.30),
1673 (AccountSubType::AdditionalPaidInCapital, "APIC", 0.20),
1674 (AccountSubType::OtherComprehensiveIncome, "OCI", 0.30),
1675 ];
1676
1677 let mut account_num = 300000u32;
1678 for (sub_type, name_prefix, weight) in sub_types {
1679 let sub_count = ((count as f64) * weight).round() as usize;
1680 for i in 0..sub_count.max(1) {
1681 let account = GLAccount::new(
1682 format!("{account_num}"),
1683 format!("{} {}", name_prefix, i + 1),
1684 AccountType::Equity,
1685 sub_type,
1686 );
1687 coa.add_account(account);
1688 account_num += 10;
1689 }
1690 }
1691 }
1692
1693 fn generate_revenue_accounts(&mut self, coa: &mut ChartOfAccounts, count: usize) {
1694 let sub_types = vec![
1695 (AccountSubType::ProductRevenue, "Product Revenue", 0.40),
1696 (AccountSubType::ServiceRevenue, "Service Revenue", 0.30),
1697 (AccountSubType::InterestIncome, "Interest Income", 0.10),
1698 (AccountSubType::OtherIncome, "Other Income", 0.20),
1699 ];
1700
1701 let mut account_num = 400000u32;
1702 for (sub_type, name_prefix, weight) in sub_types {
1703 let sub_count = ((count as f64) * weight).round() as usize;
1704 for i in 0..sub_count.max(1) {
1705 let account = GLAccount::new(
1706 format!("{account_num}"),
1707 format!("{} {}", name_prefix, i + 1),
1708 AccountType::Revenue,
1709 sub_type,
1710 );
1711 coa.add_account(account);
1712 account_num += 10;
1713 }
1714 }
1715 }
1716
1717 fn generate_expense_accounts(&mut self, coa: &mut ChartOfAccounts, count: usize) {
1718 let sub_types = vec![
1719 (AccountSubType::CostOfGoodsSold, "COGS", 0.20),
1720 (
1721 AccountSubType::OperatingExpenses,
1722 "Operating Expenses",
1723 0.25,
1724 ),
1725 (AccountSubType::SellingExpenses, "Selling Expenses", 0.15),
1726 (
1727 AccountSubType::AdministrativeExpenses,
1728 "Admin Expenses",
1729 0.15,
1730 ),
1731 (AccountSubType::DepreciationExpense, "Depreciation", 0.10),
1732 (AccountSubType::InterestExpense, "Interest Expense", 0.05),
1733 (AccountSubType::TaxExpense, "Tax Expense", 0.05),
1734 (AccountSubType::OtherExpenses, "Other Expenses", 0.05),
1735 ];
1736
1737 let mut account_num = 500000u32;
1738 for (sub_type, name_prefix, weight) in sub_types {
1739 let sub_count = ((count as f64) * weight).round() as usize;
1740 for i in 0..sub_count.max(1) {
1741 let mut account = GLAccount::new(
1742 format!("{account_num}"),
1743 format!("{} {}", name_prefix, i + 1),
1744 AccountType::Expense,
1745 sub_type,
1746 );
1747 account.requires_cost_center = true;
1748 coa.add_account(account);
1749 account_num += 10;
1750 }
1751 }
1752 }
1753
1754 fn generate_suspense_accounts(&mut self, coa: &mut ChartOfAccounts) {
1755 let suspense_types = vec![
1756 (AccountSubType::SuspenseClearing, "Suspense Clearing"),
1757 (AccountSubType::GoodsReceivedClearing, "GR/IR Clearing"),
1758 (AccountSubType::BankClearing, "Bank Clearing"),
1759 (
1760 AccountSubType::IntercompanyClearing,
1761 "Intercompany Clearing",
1762 ),
1763 ];
1764
1765 let mut account_num = 199000u32;
1766 for (sub_type, name) in suspense_types {
1767 let mut account = GLAccount::new(
1768 format!("{account_num}"),
1769 name.to_string(),
1770 AccountType::Asset,
1771 sub_type,
1772 );
1773 account.is_suspense_account = true;
1774 coa.add_account(account);
1775 account_num += 100;
1776 }
1777 }
1778}
1779
1780impl Generator for ChartOfAccountsGenerator {
1781 type Item = ChartOfAccounts;
1782 type Config = (CoAComplexity, IndustrySector);
1783
1784 fn new(config: Self::Config, seed: u64) -> Self {
1785 Self::new(config.0, config.1, seed)
1786 }
1787
1788 fn generate_one(&mut self) -> Self::Item {
1789 self.generate()
1790 }
1791
1792 fn reset(&mut self) {
1793 self.rng = seeded_rng(self.seed, 0);
1794 self.count = 0;
1795 self.coa_framework = CoAFramework::UsGaap;
1796 }
1797
1798 fn count(&self) -> u64 {
1799 self.count
1800 }
1801
1802 fn seed(&self) -> u64 {
1803 self.seed
1804 }
1805}
1806
1807#[cfg(test)]
1808#[allow(clippy::unwrap_used)]
1809mod tests {
1810 use super::*;
1811
1812 #[test]
1813 fn test_generate_small_coa() {
1814 let mut gen =
1815 ChartOfAccountsGenerator::new(CoAComplexity::Small, IndustrySector::Manufacturing, 42);
1816 let coa = gen.generate();
1817
1818 assert!(coa.account_count() >= 50);
1819 assert!(!coa.get_suspense_accounts().is_empty());
1820 }
1821
1822 #[test]
1823 fn test_generate_pcg_coa() {
1824 let mut gen =
1825 ChartOfAccountsGenerator::new(CoAComplexity::Small, IndustrySector::Manufacturing, 42)
1826 .with_french_pcg(true);
1827 let coa = gen.generate();
1828
1829 assert_eq!(coa.country, "FR");
1830 assert!(coa.name.contains("Plan Comptable") || coa.name.contains("PCG"));
1831 assert!(coa.account_count() >= 20);
1832 let first = coa.accounts.first().expect("has accounts");
1834 assert_eq!(first.account_number.len(), 6);
1835 }
1836
1837 #[test]
1841 fn test_pcg_account_structure() {
1842 let mut gen =
1843 ChartOfAccountsGenerator::new(CoAComplexity::Small, IndustrySector::Manufacturing, 42)
1844 .with_french_pcg(true);
1845 let coa = gen.generate();
1846
1847 assert_eq!(
1848 coa.account_format, "######",
1849 "PCG uses 6-digit account format"
1850 );
1851 assert!(
1852 coa.account_count() >= 20,
1853 "PCG CoA has minimum account count"
1854 );
1855
1856 let account_numbers: Vec<_> = coa
1857 .accounts
1858 .iter()
1859 .map(|a| a.account_number.as_str())
1860 .collect();
1861 for num in &account_numbers {
1862 assert_eq!(num.len(), 6, "every PCG account is 6 digits: {}", num);
1863 assert!(
1864 num.chars().all(|c| c.is_ascii_digit()),
1865 "PCG account is numeric: {}",
1866 num
1867 );
1868 }
1869
1870 let first_digits: std::collections::HashSet<char> = account_numbers
1872 .iter()
1873 .filter_map(|s| s.chars().next())
1874 .collect();
1875 let pcg_classes: std::collections::HashSet<_> =
1876 ['1', '2', '3', '4', '5', '6', '7', '8'].into();
1877 assert!(
1878 !first_digits.is_empty() && first_digits.is_subset(&pcg_classes),
1879 "PCG account numbers must be in classes 1–8, got first digits: {:?}",
1880 first_digits
1881 );
1882 }
1883}