1use super::{CompanyArchetype, Industry};
7use crate::models::{AccountMetadata, AccountNode, AccountSemantics, AccountType};
8use uuid::Uuid;
9
10#[derive(Debug, Clone)]
12pub struct ChartOfAccountsTemplate {
13 pub industry: Industry,
15 pub name: String,
17 pub accounts: Vec<AccountDefinition>,
19 pub expected_flows: Vec<ExpectedFlow>,
21}
22
23#[derive(Debug, Clone)]
25pub struct AccountDefinition {
26 pub code: String,
28 pub name: String,
30 pub account_type: AccountType,
32 pub class_id: u8,
34 pub subclass_id: u8,
36 pub parent_code: Option<String>,
38 pub semantics: u32,
40 pub typical_activity: f32,
42 pub description: String,
44}
45
46impl AccountDefinition {
47 pub fn new(
49 code: impl Into<String>,
50 name: impl Into<String>,
51 account_type: AccountType,
52 ) -> Self {
53 Self {
54 code: code.into(),
55 name: name.into(),
56 account_type,
57 class_id: 0,
58 subclass_id: 0,
59 parent_code: None,
60 semantics: 0,
61 typical_activity: 10.0,
62 description: String::new(),
63 }
64 }
65
66 pub fn with_class(mut self, class_id: u8, subclass_id: u8) -> Self {
68 self.class_id = class_id;
69 self.subclass_id = subclass_id;
70 self
71 }
72
73 pub fn with_parent(mut self, parent: impl Into<String>) -> Self {
75 self.parent_code = Some(parent.into());
76 self
77 }
78
79 pub fn with_semantics(mut self, semantics: u32) -> Self {
81 self.semantics = semantics;
82 self
83 }
84
85 pub fn with_activity(mut self, activity: f32) -> Self {
87 self.typical_activity = activity;
88 self
89 }
90
91 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
93 self.description = desc.into();
94 self
95 }
96
97 pub fn to_account(&self, index: u16) -> (AccountNode, AccountMetadata) {
99 let mut node = AccountNode::new(Uuid::new_v4(), self.account_type, index);
100 node.class_id = self.class_id;
101 node.subclass_id = self.subclass_id;
102
103 let mut metadata = AccountMetadata::new(&self.code, &self.name);
104 metadata.description = self.description.clone();
105 metadata.semantics = AccountSemantics {
106 flags: self.semantics,
107 typical_frequency: self.typical_activity,
108 avg_amount_scale: 50.0, };
110
111 (node, metadata)
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct ExpectedFlow {
118 pub from_code: String,
120 pub to_code: String,
122 pub frequency: f64,
124 pub amount_range: (f64, f64),
126 pub description: String,
128}
129
130impl ExpectedFlow {
131 pub fn new(
133 from: impl Into<String>,
134 to: impl Into<String>,
135 frequency: f64,
136 amount_range: (f64, f64),
137 ) -> Self {
138 Self {
139 from_code: from.into(),
140 to_code: to.into(),
141 frequency,
142 amount_range,
143 description: String::new(),
144 }
145 }
146
147 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
149 self.description = desc.into();
150 self
151 }
152}
153
154impl ChartOfAccountsTemplate {
155 pub fn gaap_minimal() -> Self {
157 Self {
158 industry: Industry::Retail,
159 name: "GAAP Minimal".to_string(),
160 accounts: vec![
161 AccountDefinition::new("1000", "Assets", AccountType::Asset)
163 .with_class(1, 0)
164 .with_description("Total assets"),
165 AccountDefinition::new("1100", "Cash", AccountType::Asset)
166 .with_class(1, 1)
167 .with_parent("1000")
168 .with_semantics(AccountSemantics::IS_CASH)
169 .with_activity(100.0)
170 .with_description("Cash and cash equivalents"),
171 AccountDefinition::new("1200", "Accounts Receivable", AccountType::Asset)
172 .with_class(1, 2)
173 .with_parent("1000")
174 .with_semantics(AccountSemantics::IS_RECEIVABLE)
175 .with_activity(50.0)
176 .with_description("Amounts owed by customers"),
177 AccountDefinition::new("1300", "Inventory", AccountType::Asset)
178 .with_class(1, 3)
179 .with_parent("1000")
180 .with_semantics(AccountSemantics::IS_INVENTORY)
181 .with_activity(30.0)
182 .with_description("Goods held for sale"),
183 AccountDefinition::new("1400", "Prepaid Expenses", AccountType::Asset)
184 .with_class(1, 4)
185 .with_parent("1000")
186 .with_activity(5.0)
187 .with_description("Expenses paid in advance"),
188 AccountDefinition::new("1500", "Fixed Assets", AccountType::Asset)
189 .with_class(1, 5)
190 .with_parent("1000")
191 .with_activity(2.0)
192 .with_description("Property, plant, and equipment"),
193 AccountDefinition::new("1510", "Accumulated Depreciation", AccountType::Contra)
194 .with_class(1, 5)
195 .with_parent("1500")
196 .with_semantics(AccountSemantics::IS_DEPRECIATION)
197 .with_activity(12.0)
198 .with_description("Accumulated depreciation on fixed assets"),
199 AccountDefinition::new("2000", "Liabilities", AccountType::Liability)
201 .with_class(2, 0)
202 .with_description("Total liabilities"),
203 AccountDefinition::new("2100", "Accounts Payable", AccountType::Liability)
204 .with_class(2, 1)
205 .with_parent("2000")
206 .with_semantics(AccountSemantics::IS_PAYABLE)
207 .with_activity(40.0)
208 .with_description("Amounts owed to suppliers"),
209 AccountDefinition::new("2200", "Accrued Expenses", AccountType::Liability)
210 .with_class(2, 2)
211 .with_parent("2000")
212 .with_activity(12.0)
213 .with_description("Expenses incurred but not yet paid"),
214 AccountDefinition::new("2300", "Unearned Revenue", AccountType::Liability)
215 .with_class(2, 3)
216 .with_parent("2000")
217 .with_activity(10.0)
218 .with_description("Revenue received but not yet earned"),
219 AccountDefinition::new("2400", "Notes Payable", AccountType::Liability)
220 .with_class(2, 4)
221 .with_parent("2000")
222 .with_activity(2.0)
223 .with_description("Short-term loans"),
224 AccountDefinition::new("3000", "Equity", AccountType::Equity)
226 .with_class(3, 0)
227 .with_description("Owner's equity"),
228 AccountDefinition::new("3100", "Common Stock", AccountType::Equity)
229 .with_class(3, 1)
230 .with_parent("3000")
231 .with_activity(1.0)
232 .with_description("Issued common shares"),
233 AccountDefinition::new("3200", "Retained Earnings", AccountType::Equity)
234 .with_class(3, 2)
235 .with_parent("3000")
236 .with_activity(1.0)
237 .with_description("Accumulated profits"),
238 AccountDefinition::new("4000", "Revenue", AccountType::Revenue)
240 .with_class(4, 0)
241 .with_semantics(AccountSemantics::IS_REVENUE)
242 .with_description("Total revenue"),
243 AccountDefinition::new("4100", "Sales Revenue", AccountType::Revenue)
244 .with_class(4, 1)
245 .with_parent("4000")
246 .with_semantics(AccountSemantics::IS_REVENUE)
247 .with_activity(100.0)
248 .with_description("Revenue from product sales"),
249 AccountDefinition::new("4200", "Service Revenue", AccountType::Revenue)
250 .with_class(4, 2)
251 .with_parent("4000")
252 .with_semantics(AccountSemantics::IS_REVENUE)
253 .with_activity(20.0)
254 .with_description("Revenue from services"),
255 AccountDefinition::new("5000", "Cost of Goods Sold", AccountType::Expense)
257 .with_class(5, 0)
258 .with_semantics(AccountSemantics::IS_COGS)
259 .with_activity(80.0)
260 .with_description("Direct cost of products sold"),
261 AccountDefinition::new("6000", "Operating Expenses", AccountType::Expense)
262 .with_class(6, 0)
263 .with_semantics(AccountSemantics::IS_EXPENSE)
264 .with_description("General operating expenses"),
265 AccountDefinition::new("6100", "Salaries Expense", AccountType::Expense)
266 .with_class(6, 1)
267 .with_parent("6000")
268 .with_semantics(AccountSemantics::IS_PAYROLL)
269 .with_activity(24.0)
270 .with_description("Employee salaries"),
271 AccountDefinition::new("6200", "Rent Expense", AccountType::Expense)
272 .with_class(6, 2)
273 .with_parent("6000")
274 .with_activity(12.0)
275 .with_description("Rent for facilities"),
276 AccountDefinition::new("6300", "Utilities Expense", AccountType::Expense)
277 .with_class(6, 3)
278 .with_parent("6000")
279 .with_activity(12.0)
280 .with_description("Electricity, water, gas"),
281 AccountDefinition::new("6400", "Depreciation Expense", AccountType::Expense)
282 .with_class(6, 4)
283 .with_parent("6000")
284 .with_activity(12.0)
285 .with_description("Depreciation of fixed assets"),
286 AccountDefinition::new("6500", "Marketing Expense", AccountType::Expense)
287 .with_class(6, 5)
288 .with_parent("6000")
289 .with_activity(20.0)
290 .with_description("Advertising and marketing"),
291 AccountDefinition::new("6900", "Other Expenses", AccountType::Expense)
292 .with_class(6, 9)
293 .with_parent("6000")
294 .with_activity(15.0)
295 .with_description("Miscellaneous expenses"),
296 AccountDefinition::new("9100", "Clearing Account", AccountType::Asset)
298 .with_class(9, 1)
299 .with_semantics(AccountSemantics::IS_SUSPENSE)
300 .with_activity(30.0)
301 .with_description("Temporary clearing account"),
302 ],
303 expected_flows: vec![
304 ExpectedFlow::new("4100", "1200", 0.30, (50.0, 5000.0))
306 .with_description("Credit sale: Revenue → A/R"),
307 ExpectedFlow::new("1200", "1100", 0.25, (50.0, 5000.0))
308 .with_description("Collection: A/R → Cash"),
309 ExpectedFlow::new("4100", "1100", 0.10, (20.0, 500.0))
310 .with_description("Cash sale: Revenue → Cash"),
311 ExpectedFlow::new("1300", "2100", 0.15, (100.0, 10000.0))
313 .with_description("Purchase: Inventory → A/P"),
314 ExpectedFlow::new("2100", "1100", 0.15, (100.0, 10000.0))
315 .with_description("Payment: A/P → Cash"),
316 ExpectedFlow::new("5000", "1300", 0.15, (50.0, 5000.0))
317 .with_description("Cost of sale: COGS → Inventory"),
318 ExpectedFlow::new("6100", "1100", 0.08, (5000.0, 50000.0))
320 .with_description("Payroll: Salaries → Cash"),
321 ExpectedFlow::new("6200", "1100", 0.04, (1000.0, 10000.0))
322 .with_description("Rent payment"),
323 ExpectedFlow::new("6300", "1100", 0.04, (200.0, 2000.0))
324 .with_description("Utilities payment"),
325 ExpectedFlow::new("6400", "1510", 0.04, (500.0, 5000.0))
326 .with_description("Depreciation: Expense → Accum Depr"),
327 ],
328 }
329 }
330
331 pub fn retail_standard() -> Self {
333 let mut template = Self::gaap_minimal();
334 template.industry = Industry::Retail;
335 template.name = "Retail Standard".to_string();
336
337 template.accounts.extend(vec![
339 AccountDefinition::new("1310", "Merchandise Inventory", AccountType::Asset)
341 .with_class(1, 3)
342 .with_parent("1300")
343 .with_semantics(AccountSemantics::IS_INVENTORY)
344 .with_activity(50.0),
345 AccountDefinition::new("1320", "Inventory in Transit", AccountType::Asset)
346 .with_class(1, 3)
347 .with_parent("1300")
348 .with_activity(10.0),
349 AccountDefinition::new("1110", "Cash Registers", AccountType::Asset)
351 .with_class(1, 1)
352 .with_parent("1100")
353 .with_semantics(AccountSemantics::IS_CASH)
354 .with_activity(200.0),
355 AccountDefinition::new("1120", "Undeposited Funds", AccountType::Asset)
356 .with_class(1, 1)
357 .with_parent("1100")
358 .with_semantics(AccountSemantics::IS_SUSPENSE | AccountSemantics::IS_CASH)
359 .with_activity(100.0),
360 AccountDefinition::new("1130", "Credit Card Receivable", AccountType::Asset)
362 .with_class(1, 1)
363 .with_parent("1100")
364 .with_activity(80.0),
365 AccountDefinition::new("4110", "Sales Returns", AccountType::Contra)
367 .with_class(4, 1)
368 .with_parent("4100")
369 .with_activity(20.0),
370 AccountDefinition::new("4120", "Sales Discounts", AccountType::Contra)
371 .with_class(4, 1)
372 .with_parent("4100")
373 .with_activity(15.0),
374 AccountDefinition::new("6510", "Store Supplies", AccountType::Expense)
376 .with_class(6, 5)
377 .with_parent("6500")
378 .with_activity(10.0),
379 AccountDefinition::new("6520", "Credit Card Fees", AccountType::Expense)
380 .with_class(6, 5)
381 .with_parent("6500")
382 .with_activity(30.0),
383 AccountDefinition::new("6530", "Shrinkage", AccountType::Expense)
384 .with_class(6, 5)
385 .with_parent("6500")
386 .with_activity(12.0),
387 ]);
388
389 template.expected_flows.extend(vec![
391 ExpectedFlow::new("1110", "1120", 0.20, (100.0, 5000.0))
392 .with_description("Register → Undeposited"),
393 ExpectedFlow::new("1120", "1100", 0.15, (1000.0, 20000.0))
394 .with_description("Bank deposit"),
395 ExpectedFlow::new("4110", "1200", 0.05, (20.0, 500.0)).with_description("Sales return"),
396 ExpectedFlow::new("6520", "1130", 0.08, (50.0, 500.0))
397 .with_description("CC processing fees"),
398 ]);
399
400 template
401 }
402
403 pub fn saas_standard() -> Self {
405 let mut template = Self::gaap_minimal();
406 template.industry = Industry::SaaS;
407 template.name = "SaaS Standard".to_string();
408
409 template.accounts.extend(vec![
411 AccountDefinition::new("2310", "Deferred Revenue - Current", AccountType::Liability)
413 .with_class(2, 3)
414 .with_parent("2300")
415 .with_activity(100.0),
416 AccountDefinition::new(
417 "2320",
418 "Deferred Revenue - Long-term",
419 AccountType::Liability,
420 )
421 .with_class(2, 3)
422 .with_parent("2300")
423 .with_activity(20.0),
424 AccountDefinition::new("4110", "Subscription Revenue", AccountType::Revenue)
426 .with_class(4, 1)
427 .with_parent("4100")
428 .with_semantics(AccountSemantics::IS_REVENUE)
429 .with_activity(200.0),
430 AccountDefinition::new("4120", "Professional Services", AccountType::Revenue)
431 .with_class(4, 2)
432 .with_parent("4200")
433 .with_activity(20.0),
434 AccountDefinition::new("5100", "Hosting Costs", AccountType::Expense)
436 .with_class(5, 1)
437 .with_parent("5000")
438 .with_activity(12.0),
439 AccountDefinition::new("5200", "Third-party Software", AccountType::Expense)
440 .with_class(5, 2)
441 .with_parent("5000")
442 .with_activity(24.0),
443 AccountDefinition::new("6510", "Customer Acquisition Cost", AccountType::Expense)
445 .with_class(6, 5)
446 .with_parent("6500")
447 .with_activity(50.0),
448 AccountDefinition::new("1410", "Deferred Commission", AccountType::Asset)
449 .with_class(1, 4)
450 .with_parent("1400")
451 .with_activity(30.0),
452 ]);
453
454 template.expected_flows = vec![
456 ExpectedFlow::new("1100", "2310", 0.25, (500.0, 50000.0))
457 .with_description("Annual subscription cash → Deferred revenue"),
458 ExpectedFlow::new("2310", "4110", 0.30, (50.0, 5000.0))
459 .with_description("Revenue recognition"),
460 ExpectedFlow::new("4110", "1200", 0.10, (100.0, 10000.0))
461 .with_description("Invoiced subscription"),
462 ExpectedFlow::new("1200", "1100", 0.15, (100.0, 10000.0))
463 .with_description("Collection"),
464 ExpectedFlow::new("5100", "2100", 0.08, (5000.0, 50000.0))
465 .with_description("Hosting invoice"),
466 ExpectedFlow::new("2100", "1100", 0.10, (1000.0, 50000.0))
467 .with_description("Vendor payment"),
468 ExpectedFlow::new("6510", "1100", 0.05, (1000.0, 20000.0))
469 .with_description("Marketing spend"),
470 ];
471
472 template
473 }
474
475 pub fn for_archetype(archetype: &CompanyArchetype) -> Self {
477 match archetype.industry() {
478 Industry::Retail => Self::retail_standard(),
479 Industry::SaaS => Self::saas_standard(),
480 Industry::Manufacturing => Self::gaap_minimal(), Industry::ProfessionalServices => Self::gaap_minimal(), Industry::FinancialServices => Self::gaap_minimal(), _ => Self::gaap_minimal(),
484 }
485 }
486
487 pub fn account_count(&self) -> usize {
489 self.accounts.len()
490 }
491
492 pub fn find_by_code(&self, code: &str) -> Option<&AccountDefinition> {
494 self.accounts.iter().find(|a| a.code == code)
495 }
496
497 pub fn accounts_by_type(&self, account_type: AccountType) -> Vec<&AccountDefinition> {
499 self.accounts
500 .iter()
501 .filter(|a| a.account_type == account_type)
502 .collect()
503 }
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn test_gaap_minimal() {
512 let template = ChartOfAccountsTemplate::gaap_minimal();
513 assert!(template.account_count() > 20);
514
515 assert!(!template.accounts_by_type(AccountType::Asset).is_empty());
517 assert!(!template.accounts_by_type(AccountType::Liability).is_empty());
518 assert!(!template.accounts_by_type(AccountType::Revenue).is_empty());
519 assert!(!template.accounts_by_type(AccountType::Expense).is_empty());
520 }
521
522 #[test]
523 fn test_retail_template() {
524 let template = ChartOfAccountsTemplate::retail_standard();
525 assert!(template.account_count() > 30);
526 assert_eq!(template.industry, Industry::Retail);
527
528 assert!(template.find_by_code("1110").is_some()); assert!(template.find_by_code("1120").is_some()); }
532
533 #[test]
534 fn test_saas_template() {
535 let template = ChartOfAccountsTemplate::saas_standard();
536 assert_eq!(template.industry, Industry::SaaS);
537
538 assert!(template.find_by_code("2310").is_some()); assert!(template.find_by_code("4110").is_some()); }
542}