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