j-law-ruby 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/README.md +109 -0
  4. data/Rakefile +87 -0
  5. data/ext/j_law_ruby/extconf.rb +34 -0
  6. data/lib/j_law_ruby/build_support.rb +129 -0
  7. data/lib/j_law_ruby/c_ffi.rb +662 -0
  8. data/lib/j_law_ruby.rb +532 -0
  9. data/rake_support/vendor_rust.rb +51 -0
  10. data/test/test_c_ffi_adapter.rb +46 -0
  11. data/test/test_consumption_tax.rb +66 -0
  12. data/test/test_gemspec.rb +82 -0
  13. data/test/test_income_tax.rb +77 -0
  14. data/test/test_income_tax_deductions.rb +82 -0
  15. data/test/test_real_estate.rb +98 -0
  16. data/test/test_stamp_tax.rb +68 -0
  17. data/test/test_withholding_tax.rb +65 -0
  18. data/vendor/rust/Cargo.lock +235 -0
  19. data/vendor/rust/Cargo.toml +12 -0
  20. data/vendor/rust/crates/j-law-c-ffi/Cargo.toml +20 -0
  21. data/vendor/rust/crates/j-law-c-ffi/j_law_c_ffi.h +493 -0
  22. data/vendor/rust/crates/j-law-c-ffi/src/lib.rs +1553 -0
  23. data/vendor/rust/crates/j-law-core/Cargo.toml +18 -0
  24. data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/calculator.rs +216 -0
  25. data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/context.rs +29 -0
  26. data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/mod.rs +9 -0
  27. data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/params.rs +24 -0
  28. data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/policy.rs +34 -0
  29. data/vendor/rust/crates/j-law-core/src/domains/income_tax/assessment.rs +76 -0
  30. data/vendor/rust/crates/j-law-core/src/domains/income_tax/calculator.rs +222 -0
  31. data/vendor/rust/crates/j-law-core/src/domains/income_tax/context.rs +79 -0
  32. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/calculator.rs +167 -0
  33. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/context.rs +172 -0
  34. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/expense.rs +465 -0
  35. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/mod.rs +20 -0
  36. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/params.rs +205 -0
  37. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/personal.rs +324 -0
  38. data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/types.rs +61 -0
  39. data/vendor/rust/crates/j-law-core/src/domains/income_tax/mod.rs +24 -0
  40. data/vendor/rust/crates/j-law-core/src/domains/income_tax/params.rs +109 -0
  41. data/vendor/rust/crates/j-law-core/src/domains/income_tax/policy.rs +103 -0
  42. data/vendor/rust/crates/j-law-core/src/domains/mod.rs +5 -0
  43. data/vendor/rust/crates/j-law-core/src/domains/real_estate/calculator.rs +197 -0
  44. data/vendor/rust/crates/j-law-core/src/domains/real_estate/context.rs +48 -0
  45. data/vendor/rust/crates/j-law-core/src/domains/real_estate/mod.rs +9 -0
  46. data/vendor/rust/crates/j-law-core/src/domains/real_estate/params.rs +43 -0
  47. data/vendor/rust/crates/j-law-core/src/domains/real_estate/policy.rs +40 -0
  48. data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/calculator.rs +321 -0
  49. data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/context.rs +408 -0
  50. data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/mod.rs +12 -0
  51. data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/params.rs +190 -0
  52. data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/policy.rs +105 -0
  53. data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/calculator.rs +247 -0
  54. data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/context.rs +167 -0
  55. data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/mod.rs +9 -0
  56. data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/params.rs +80 -0
  57. data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/policy.rs +49 -0
  58. data/vendor/rust/crates/j-law-core/src/error.rs +171 -0
  59. data/vendor/rust/crates/j-law-core/src/lib.rs +9 -0
  60. data/vendor/rust/crates/j-law-core/src/types/amount.rs +232 -0
  61. data/vendor/rust/crates/j-law-core/src/types/citation.rs +82 -0
  62. data/vendor/rust/crates/j-law-core/src/types/date.rs +280 -0
  63. data/vendor/rust/crates/j-law-core/src/types/mod.rs +11 -0
  64. data/vendor/rust/crates/j-law-core/src/types/rate.rs +219 -0
  65. data/vendor/rust/crates/j-law-core/src/types/rounding.rs +81 -0
  66. data/vendor/rust/crates/j-law-registry/Cargo.toml +15 -0
  67. data/vendor/rust/crates/j-law-registry/data/consumption_tax/consumption_tax.json +70 -0
  68. data/vendor/rust/crates/j-law-registry/data/income_tax/deductions.json +327 -0
  69. data/vendor/rust/crates/j-law-registry/data/income_tax/income_tax.json +352 -0
  70. data/vendor/rust/crates/j-law-registry/data/real_estate/brokerage_fee.json +125 -0
  71. data/vendor/rust/crates/j-law-registry/data/stamp_tax/stamp_tax.json +674 -0
  72. data/vendor/rust/crates/j-law-registry/data/withholding_tax/withholding_tax.json +70 -0
  73. data/vendor/rust/crates/j-law-registry/src/consumption_tax_loader.rs +325 -0
  74. data/vendor/rust/crates/j-law-registry/src/consumption_tax_schema.rs +49 -0
  75. data/vendor/rust/crates/j-law-registry/src/income_tax_deduction_loader.rs +636 -0
  76. data/vendor/rust/crates/j-law-registry/src/income_tax_deduction_schema.rs +111 -0
  77. data/vendor/rust/crates/j-law-registry/src/income_tax_loader.rs +445 -0
  78. data/vendor/rust/crates/j-law-registry/src/income_tax_schema.rs +44 -0
  79. data/vendor/rust/crates/j-law-registry/src/lib.rs +20 -0
  80. data/vendor/rust/crates/j-law-registry/src/loader.rs +221 -0
  81. data/vendor/rust/crates/j-law-registry/src/schema.rs +73 -0
  82. data/vendor/rust/crates/j-law-registry/src/stamp_tax_loader.rs +374 -0
  83. data/vendor/rust/crates/j-law-registry/src/stamp_tax_schema.rs +72 -0
  84. data/vendor/rust/crates/j-law-registry/src/validator.rs +204 -0
  85. data/vendor/rust/crates/j-law-registry/src/withholding_tax_loader.rs +310 -0
  86. data/vendor/rust/crates/j-law-registry/src/withholding_tax_schema.rs +61 -0
  87. metadata +148 -0
@@ -0,0 +1,408 @@
1
+ use std::collections::HashSet;
2
+ use std::fmt;
3
+ use std::str::FromStr;
4
+
5
+ use crate::domains::stamp_tax::policy::StampTaxPolicy;
6
+ use crate::error::InputError;
7
+ use crate::types::date::LegalDate;
8
+
9
+ /// 印紙税の文書コード。
10
+ ///
11
+ /// 別表第一の税額表において税額行が変わる単位で定義する。
12
+ ///
13
+ /// # 法的根拠
14
+ /// 印紙税法 別表第一
15
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16
+ pub enum StampTaxDocumentCode {
17
+ Article1RealEstateTransfer,
18
+ Article1OtherTransfer,
19
+ Article1LandLeaseOrSurfaceRight,
20
+ Article1ConsumptionLoan,
21
+ Article1Transportation,
22
+ Article2ConstructionWork,
23
+ Article2GeneralContract,
24
+ Article3BillAmountTable,
25
+ Article3BillSpecialFlat200,
26
+ Article4SecurityCertificate,
27
+ Article5MergerOrSplit,
28
+ Article6ArticlesOfIncorporation,
29
+ Article7ContinuingTransactionBasic,
30
+ Article8DepositCertificate,
31
+ Article9TransportCertificate,
32
+ Article10InsuranceCertificate,
33
+ Article11LetterOfCredit,
34
+ Article12TrustContract,
35
+ Article13DebtGuarantee,
36
+ Article14DepositContract,
37
+ Article15AssignmentOrAssumption,
38
+ Article16DividendReceipt,
39
+ Article17SalesReceipt,
40
+ Article17OtherReceipt,
41
+ Article18Passbook,
42
+ Article19MiscPassbook,
43
+ Article20SealBook,
44
+ }
45
+
46
+ impl StampTaxDocumentCode {
47
+ /// 永続化・外部API向けの安定文字列コード。
48
+ pub fn as_str(self) -> &'static str {
49
+ match self {
50
+ Self::Article1RealEstateTransfer => "article1_real_estate_transfer",
51
+ Self::Article1OtherTransfer => "article1_other_transfer",
52
+ Self::Article1LandLeaseOrSurfaceRight => "article1_land_lease_or_surface_right",
53
+ Self::Article1ConsumptionLoan => "article1_consumption_loan",
54
+ Self::Article1Transportation => "article1_transportation",
55
+ Self::Article2ConstructionWork => "article2_construction_work",
56
+ Self::Article2GeneralContract => "article2_general_contract",
57
+ Self::Article3BillAmountTable => "article3_bill_amount_table",
58
+ Self::Article3BillSpecialFlat200 => "article3_bill_special_flat_200",
59
+ Self::Article4SecurityCertificate => "article4_security_certificate",
60
+ Self::Article5MergerOrSplit => "article5_merger_or_split",
61
+ Self::Article6ArticlesOfIncorporation => "article6_articles_of_incorporation",
62
+ Self::Article7ContinuingTransactionBasic => "article7_continuing_transaction_basic",
63
+ Self::Article8DepositCertificate => "article8_deposit_certificate",
64
+ Self::Article9TransportCertificate => "article9_transport_certificate",
65
+ Self::Article10InsuranceCertificate => "article10_insurance_certificate",
66
+ Self::Article11LetterOfCredit => "article11_letter_of_credit",
67
+ Self::Article12TrustContract => "article12_trust_contract",
68
+ Self::Article13DebtGuarantee => "article13_debt_guarantee",
69
+ Self::Article14DepositContract => "article14_deposit_contract",
70
+ Self::Article15AssignmentOrAssumption => "article15_assignment_or_assumption",
71
+ Self::Article16DividendReceipt => "article16_dividend_receipt",
72
+ Self::Article17SalesReceipt => "article17_sales_receipt",
73
+ Self::Article17OtherReceipt => "article17_other_receipt",
74
+ Self::Article18Passbook => "article18_passbook",
75
+ Self::Article19MiscPassbook => "article19_misc_passbook",
76
+ Self::Article20SealBook => "article20_seal_book",
77
+ }
78
+ }
79
+
80
+ /// C ABI 向けの整数コード。
81
+ pub fn ffi_code(self) -> u32 {
82
+ match self {
83
+ Self::Article1RealEstateTransfer => 1,
84
+ Self::Article1OtherTransfer => 2,
85
+ Self::Article1LandLeaseOrSurfaceRight => 3,
86
+ Self::Article1ConsumptionLoan => 4,
87
+ Self::Article1Transportation => 5,
88
+ Self::Article2ConstructionWork => 6,
89
+ Self::Article2GeneralContract => 7,
90
+ Self::Article3BillAmountTable => 8,
91
+ Self::Article3BillSpecialFlat200 => 9,
92
+ Self::Article4SecurityCertificate => 10,
93
+ Self::Article5MergerOrSplit => 11,
94
+ Self::Article6ArticlesOfIncorporation => 12,
95
+ Self::Article7ContinuingTransactionBasic => 13,
96
+ Self::Article8DepositCertificate => 14,
97
+ Self::Article9TransportCertificate => 15,
98
+ Self::Article10InsuranceCertificate => 16,
99
+ Self::Article11LetterOfCredit => 17,
100
+ Self::Article12TrustContract => 18,
101
+ Self::Article13DebtGuarantee => 19,
102
+ Self::Article14DepositContract => 20,
103
+ Self::Article15AssignmentOrAssumption => 21,
104
+ Self::Article16DividendReceipt => 22,
105
+ Self::Article17SalesReceipt => 23,
106
+ Self::Article17OtherReceipt => 24,
107
+ Self::Article18Passbook => 25,
108
+ Self::Article19MiscPassbook => 26,
109
+ Self::Article20SealBook => 27,
110
+ }
111
+ }
112
+
113
+ /// C ABI の整数コードを Rust enum に戻す。
114
+ pub fn from_ffi_code(code: u32) -> Result<Self, InputError> {
115
+ match code {
116
+ 1 => Ok(Self::Article1RealEstateTransfer),
117
+ 2 => Ok(Self::Article1OtherTransfer),
118
+ 3 => Ok(Self::Article1LandLeaseOrSurfaceRight),
119
+ 4 => Ok(Self::Article1ConsumptionLoan),
120
+ 5 => Ok(Self::Article1Transportation),
121
+ 6 => Ok(Self::Article2ConstructionWork),
122
+ 7 => Ok(Self::Article2GeneralContract),
123
+ 8 => Ok(Self::Article3BillAmountTable),
124
+ 9 => Ok(Self::Article3BillSpecialFlat200),
125
+ 10 => Ok(Self::Article4SecurityCertificate),
126
+ 11 => Ok(Self::Article5MergerOrSplit),
127
+ 12 => Ok(Self::Article6ArticlesOfIncorporation),
128
+ 13 => Ok(Self::Article7ContinuingTransactionBasic),
129
+ 14 => Ok(Self::Article8DepositCertificate),
130
+ 15 => Ok(Self::Article9TransportCertificate),
131
+ 16 => Ok(Self::Article10InsuranceCertificate),
132
+ 17 => Ok(Self::Article11LetterOfCredit),
133
+ 18 => Ok(Self::Article12TrustContract),
134
+ 19 => Ok(Self::Article13DebtGuarantee),
135
+ 20 => Ok(Self::Article14DepositContract),
136
+ 21 => Ok(Self::Article15AssignmentOrAssumption),
137
+ 22 => Ok(Self::Article16DividendReceipt),
138
+ 23 => Ok(Self::Article17SalesReceipt),
139
+ 24 => Ok(Self::Article17OtherReceipt),
140
+ 25 => Ok(Self::Article18Passbook),
141
+ 26 => Ok(Self::Article19MiscPassbook),
142
+ 27 => Ok(Self::Article20SealBook),
143
+ _ => Err(InputError::InvalidStampTaxInput {
144
+ field: "document_code".into(),
145
+ reason: format!("未知の印紙税文書コードです: {code}"),
146
+ }),
147
+ }
148
+ }
149
+ }
150
+
151
+ impl fmt::Display for StampTaxDocumentCode {
152
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153
+ f.write_str(self.as_str())
154
+ }
155
+ }
156
+
157
+ impl From<StampTaxDocumentCode> for u32 {
158
+ fn from(value: StampTaxDocumentCode) -> Self {
159
+ value.ffi_code()
160
+ }
161
+ }
162
+
163
+ impl FromStr for StampTaxDocumentCode {
164
+ type Err = InputError;
165
+
166
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
167
+ match s {
168
+ "article1_real_estate_transfer" => Ok(Self::Article1RealEstateTransfer),
169
+ "article1_other_transfer" => Ok(Self::Article1OtherTransfer),
170
+ "article1_land_lease_or_surface_right" => Ok(Self::Article1LandLeaseOrSurfaceRight),
171
+ "article1_consumption_loan" => Ok(Self::Article1ConsumptionLoan),
172
+ "article1_transportation" => Ok(Self::Article1Transportation),
173
+ "article2_construction_work" => Ok(Self::Article2ConstructionWork),
174
+ "article2_general_contract" => Ok(Self::Article2GeneralContract),
175
+ "article3_bill_amount_table" => Ok(Self::Article3BillAmountTable),
176
+ "article3_bill_special_flat_200" => Ok(Self::Article3BillSpecialFlat200),
177
+ "article4_security_certificate" => Ok(Self::Article4SecurityCertificate),
178
+ "article5_merger_or_split" => Ok(Self::Article5MergerOrSplit),
179
+ "article6_articles_of_incorporation" => Ok(Self::Article6ArticlesOfIncorporation),
180
+ "article7_continuing_transaction_basic" => Ok(Self::Article7ContinuingTransactionBasic),
181
+ "article8_deposit_certificate" => Ok(Self::Article8DepositCertificate),
182
+ "article9_transport_certificate" => Ok(Self::Article9TransportCertificate),
183
+ "article10_insurance_certificate" => Ok(Self::Article10InsuranceCertificate),
184
+ "article11_letter_of_credit" => Ok(Self::Article11LetterOfCredit),
185
+ "article12_trust_contract" => Ok(Self::Article12TrustContract),
186
+ "article13_debt_guarantee" => Ok(Self::Article13DebtGuarantee),
187
+ "article14_deposit_contract" => Ok(Self::Article14DepositContract),
188
+ "article15_assignment_or_assumption" => Ok(Self::Article15AssignmentOrAssumption),
189
+ "article16_dividend_receipt" => Ok(Self::Article16DividendReceipt),
190
+ "article17_sales_receipt" => Ok(Self::Article17SalesReceipt),
191
+ "article17_other_receipt" => Ok(Self::Article17OtherReceipt),
192
+ "article18_passbook" => Ok(Self::Article18Passbook),
193
+ "article19_misc_passbook" => Ok(Self::Article19MiscPassbook),
194
+ "article20_seal_book" => Ok(Self::Article20SealBook),
195
+ _ => Err(InputError::InvalidStampTaxInput {
196
+ field: "document_code".into(),
197
+ reason: format!("未知の印紙税文書コードです: {s}"),
198
+ }),
199
+ }
200
+ }
201
+ }
202
+
203
+ /// 印紙税の適用フラグ。
204
+ ///
205
+ /// WARNING: このフラグの事実認定はライブラリの責任範囲外です。
206
+ /// 呼び出し元が正しく判断した上で指定してください。
207
+ ///
208
+ /// # 法的根拠
209
+ /// 印紙税法 別表第一
210
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
211
+ pub enum StampTaxFlag {
212
+ Article3CopyOrTranscriptExempt,
213
+ Article4SpecifiedIssuerExempt,
214
+ Article4RestrictedBeneficiaryCertificateExempt,
215
+ Article6NotaryCopyExempt,
216
+ Article8SmallDepositExempt,
217
+ Article13IdentityGuaranteeExempt,
218
+ Article17NonBusinessExempt,
219
+ Article17AppendedReceiptExempt,
220
+ Article18SpecifiedFinancialInstitutionExempt,
221
+ Article18IncomeTaxExemptPassbook,
222
+ Article18TaxReserveDepositPassbook,
223
+ }
224
+
225
+ impl StampTaxFlag {
226
+ pub fn as_str(self) -> &'static str {
227
+ match self {
228
+ Self::Article3CopyOrTranscriptExempt => "article3_copy_or_transcript_exempt",
229
+ Self::Article4SpecifiedIssuerExempt => "article4_specified_issuer_exempt",
230
+ Self::Article4RestrictedBeneficiaryCertificateExempt => {
231
+ "article4_restricted_beneficiary_certificate_exempt"
232
+ }
233
+ Self::Article6NotaryCopyExempt => "article6_notary_copy_exempt",
234
+ Self::Article8SmallDepositExempt => "article8_small_deposit_exempt",
235
+ Self::Article13IdentityGuaranteeExempt => "article13_identity_guarantee_exempt",
236
+ Self::Article17NonBusinessExempt => "article17_non_business_exempt",
237
+ Self::Article17AppendedReceiptExempt => "article17_appended_receipt_exempt",
238
+ Self::Article18SpecifiedFinancialInstitutionExempt => {
239
+ "article18_specified_financial_institution_exempt"
240
+ }
241
+ Self::Article18IncomeTaxExemptPassbook => "article18_income_tax_exempt_passbook",
242
+ Self::Article18TaxReserveDepositPassbook => "article18_tax_reserve_deposit_passbook",
243
+ }
244
+ }
245
+
246
+ pub fn bitmask(self) -> u64 {
247
+ match self {
248
+ Self::Article3CopyOrTranscriptExempt => 1_u64 << 0,
249
+ Self::Article4SpecifiedIssuerExempt => 1_u64 << 1,
250
+ Self::Article4RestrictedBeneficiaryCertificateExempt => 1_u64 << 2,
251
+ Self::Article6NotaryCopyExempt => 1_u64 << 3,
252
+ Self::Article8SmallDepositExempt => 1_u64 << 4,
253
+ Self::Article13IdentityGuaranteeExempt => 1_u64 << 5,
254
+ Self::Article17NonBusinessExempt => 1_u64 << 6,
255
+ Self::Article17AppendedReceiptExempt => 1_u64 << 7,
256
+ Self::Article18SpecifiedFinancialInstitutionExempt => 1_u64 << 8,
257
+ Self::Article18IncomeTaxExemptPassbook => 1_u64 << 9,
258
+ Self::Article18TaxReserveDepositPassbook => 1_u64 << 10,
259
+ }
260
+ }
261
+
262
+ pub fn from_bitmask(mask: u64) -> Result<HashSet<Self>, InputError> {
263
+ let mut flags = HashSet::new();
264
+ let mut remaining = mask;
265
+
266
+ for flag in Self::all() {
267
+ if remaining & flag.bitmask() != 0 {
268
+ flags.insert(*flag);
269
+ remaining &= !flag.bitmask();
270
+ }
271
+ }
272
+
273
+ if remaining != 0 {
274
+ return Err(InputError::InvalidStampTaxInput {
275
+ field: "flags".into(),
276
+ reason: format!("未知の印紙税フラグビットです: 0x{remaining:x}"),
277
+ });
278
+ }
279
+
280
+ Ok(flags)
281
+ }
282
+
283
+ pub fn all() -> &'static [Self] {
284
+ &[
285
+ Self::Article3CopyOrTranscriptExempt,
286
+ Self::Article4SpecifiedIssuerExempt,
287
+ Self::Article4RestrictedBeneficiaryCertificateExempt,
288
+ Self::Article6NotaryCopyExempt,
289
+ Self::Article8SmallDepositExempt,
290
+ Self::Article13IdentityGuaranteeExempt,
291
+ Self::Article17NonBusinessExempt,
292
+ Self::Article17AppendedReceiptExempt,
293
+ Self::Article18SpecifiedFinancialInstitutionExempt,
294
+ Self::Article18IncomeTaxExemptPassbook,
295
+ Self::Article18TaxReserveDepositPassbook,
296
+ ]
297
+ }
298
+
299
+ pub fn allowed_document_codes(self) -> &'static [StampTaxDocumentCode] {
300
+ match self {
301
+ Self::Article3CopyOrTranscriptExempt => {
302
+ &[StampTaxDocumentCode::Article3BillAmountTable]
303
+ }
304
+ Self::Article4SpecifiedIssuerExempt
305
+ | Self::Article4RestrictedBeneficiaryCertificateExempt => {
306
+ &[StampTaxDocumentCode::Article4SecurityCertificate]
307
+ }
308
+ Self::Article6NotaryCopyExempt => {
309
+ &[StampTaxDocumentCode::Article6ArticlesOfIncorporation]
310
+ }
311
+ Self::Article8SmallDepositExempt => &[StampTaxDocumentCode::Article8DepositCertificate],
312
+ Self::Article13IdentityGuaranteeExempt => {
313
+ &[StampTaxDocumentCode::Article13DebtGuarantee]
314
+ }
315
+ Self::Article17NonBusinessExempt | Self::Article17AppendedReceiptExempt => {
316
+ &[StampTaxDocumentCode::Article17SalesReceipt]
317
+ }
318
+ Self::Article18SpecifiedFinancialInstitutionExempt
319
+ | Self::Article18IncomeTaxExemptPassbook
320
+ | Self::Article18TaxReserveDepositPassbook => {
321
+ &[StampTaxDocumentCode::Article18Passbook]
322
+ }
323
+ }
324
+ }
325
+
326
+ pub fn requires_stated_amount(self) -> bool {
327
+ matches!(self, Self::Article8SmallDepositExempt)
328
+ }
329
+ }
330
+
331
+ impl fmt::Display for StampTaxFlag {
332
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333
+ f.write_str(self.as_str())
334
+ }
335
+ }
336
+
337
+ impl FromStr for StampTaxFlag {
338
+ type Err = InputError;
339
+
340
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
341
+ match s {
342
+ "article3_copy_or_transcript_exempt" => Ok(Self::Article3CopyOrTranscriptExempt),
343
+ "article4_specified_issuer_exempt" => Ok(Self::Article4SpecifiedIssuerExempt),
344
+ "article4_restricted_beneficiary_certificate_exempt" => {
345
+ Ok(Self::Article4RestrictedBeneficiaryCertificateExempt)
346
+ }
347
+ "article6_notary_copy_exempt" => Ok(Self::Article6NotaryCopyExempt),
348
+ "article8_small_deposit_exempt" => Ok(Self::Article8SmallDepositExempt),
349
+ "article13_identity_guarantee_exempt" => Ok(Self::Article13IdentityGuaranteeExempt),
350
+ "article17_non_business_exempt" => Ok(Self::Article17NonBusinessExempt),
351
+ "article17_appended_receipt_exempt" => Ok(Self::Article17AppendedReceiptExempt),
352
+ "article18_specified_financial_institution_exempt" => {
353
+ Ok(Self::Article18SpecifiedFinancialInstitutionExempt)
354
+ }
355
+ "article18_income_tax_exempt_passbook" => Ok(Self::Article18IncomeTaxExemptPassbook),
356
+ "article18_tax_reserve_deposit_passbook" => {
357
+ Ok(Self::Article18TaxReserveDepositPassbook)
358
+ }
359
+ _ => Err(InputError::InvalidStampTaxInput {
360
+ field: "flags".into(),
361
+ reason: format!("未知の印紙税フラグです: {s}"),
362
+ }),
363
+ }
364
+ }
365
+ }
366
+
367
+ /// 印紙税計算のコンテキスト。
368
+ ///
369
+ /// # 法的根拠
370
+ /// 印紙税法 第2条(課税文書)/ 別表第一(課税物件表)
371
+ pub struct StampTaxContext {
372
+ /// 印紙税の文書コード。
373
+ pub document_code: StampTaxDocumentCode,
374
+ /// 記載金額、券面金額、受取金額などの金額。
375
+ pub stated_amount: Option<u64>,
376
+ /// 契約書等の作成日。
377
+ pub target_date: LegalDate,
378
+ /// 主な非課税文書に対応する適用フラグ。
379
+ pub flags: HashSet<StampTaxFlag>,
380
+ /// 特例適用判定ポリシー。
381
+ pub policy: Box<dyn StampTaxPolicy>,
382
+ }
383
+
384
+ #[cfg(test)]
385
+ #[allow(clippy::disallowed_methods)]
386
+ mod tests {
387
+ use super::*;
388
+
389
+ #[test]
390
+ fn document_code_roundtrip() {
391
+ let code = StampTaxDocumentCode::from_str("article17_sales_receipt").unwrap();
392
+ assert_eq!(code, StampTaxDocumentCode::Article17SalesReceipt);
393
+ assert_eq!(code.as_str(), "article17_sales_receipt");
394
+ assert_eq!(
395
+ StampTaxDocumentCode::from_ffi_code(code.ffi_code()).unwrap(),
396
+ code
397
+ );
398
+ }
399
+
400
+ #[test]
401
+ fn flag_roundtrip_from_bitmask() {
402
+ let mask = StampTaxFlag::Article3CopyOrTranscriptExempt.bitmask()
403
+ | StampTaxFlag::Article17AppendedReceiptExempt.bitmask();
404
+ let flags = StampTaxFlag::from_bitmask(mask).unwrap();
405
+ assert!(flags.contains(&StampTaxFlag::Article3CopyOrTranscriptExempt));
406
+ assert!(flags.contains(&StampTaxFlag::Article17AppendedReceiptExempt));
407
+ }
408
+ }
@@ -0,0 +1,12 @@
1
+ pub mod calculator;
2
+ pub mod context;
3
+ pub mod params;
4
+ pub mod policy;
5
+
6
+ pub use calculator::{calculate_stamp_tax, StampTaxBreakdownStep, StampTaxResult};
7
+ pub use context::{StampTaxContext, StampTaxDocumentCode, StampTaxFlag};
8
+ pub use params::{
9
+ StampTaxAmountUsage, StampTaxBracket, StampTaxChargeMode, StampTaxCitation,
10
+ StampTaxDocumentParams, StampTaxParams, StampTaxSpecialRule,
11
+ };
12
+ pub use policy::StandardNtaPolicy;
@@ -0,0 +1,190 @@
1
+ use std::collections::BTreeMap;
2
+ use std::str::FromStr;
3
+
4
+ use crate::domains::stamp_tax::context::{StampTaxDocumentCode, StampTaxFlag};
5
+ use crate::error::InputError;
6
+
7
+ /// 税額適用の法的根拠。
8
+ #[derive(Debug, Clone)]
9
+ pub struct StampTaxCitation {
10
+ pub law_name: String,
11
+ pub article: String,
12
+ }
13
+
14
+ /// 印紙税額のブラケット(1区間分)。
15
+ ///
16
+ /// # 法的根拠
17
+ /// 印紙税法 別表第一
18
+ #[derive(Debug, Clone)]
19
+ pub struct StampTaxBracket {
20
+ pub label: String,
21
+ pub amount_from: u64,
22
+ pub amount_to_inclusive: Option<u64>,
23
+ pub tax_amount: u64,
24
+ }
25
+
26
+ impl StampTaxBracket {
27
+ pub fn matches(&self, amount: u64) -> bool {
28
+ amount >= self.amount_from
29
+ && match self.amount_to_inclusive {
30
+ Some(to) => amount <= to,
31
+ None => true,
32
+ }
33
+ }
34
+ }
35
+
36
+ /// 税額表の課税モード。
37
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
38
+ pub enum StampTaxChargeMode {
39
+ AmountBrackets,
40
+ FixedPerDocument,
41
+ FixedPerYear,
42
+ }
43
+
44
+ impl FromStr for StampTaxChargeMode {
45
+ type Err = InputError;
46
+
47
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
48
+ match s {
49
+ "amount_brackets" => Ok(Self::AmountBrackets),
50
+ "fixed_per_document" => Ok(Self::FixedPerDocument),
51
+ "fixed_per_year" => Ok(Self::FixedPerYear),
52
+ _ => Err(InputError::InvalidStampTaxInput {
53
+ field: "charge_mode".into(),
54
+ reason: format!("未知の charge_mode です: {s}"),
55
+ }),
56
+ }
57
+ }
58
+ }
59
+
60
+ /// 文書コードごとの金額入力の扱い。
61
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
62
+ pub enum StampTaxAmountUsage {
63
+ Required,
64
+ Optional,
65
+ Unsupported,
66
+ }
67
+
68
+ impl FromStr for StampTaxAmountUsage {
69
+ type Err = InputError;
70
+
71
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
72
+ match s {
73
+ "required" => Ok(Self::Required),
74
+ "optional" => Ok(Self::Optional),
75
+ "unsupported" => Ok(Self::Unsupported),
76
+ _ => Err(InputError::InvalidStampTaxInput {
77
+ field: "amount_usage".into(),
78
+ reason: format!("未知の amount_usage です: {s}"),
79
+ }),
80
+ }
81
+ }
82
+ }
83
+
84
+ /// 軽減・非課税などの特例ルール。
85
+ #[derive(Debug, Clone)]
86
+ pub struct StampTaxSpecialRule {
87
+ pub code: String,
88
+ pub label: String,
89
+ pub priority: u16,
90
+ pub effective_from: Option<String>,
91
+ pub effective_until: Option<String>,
92
+ pub required_flags: Vec<StampTaxFlag>,
93
+ pub tax_amount: Option<u64>,
94
+ pub rule_label: Option<String>,
95
+ pub brackets: Vec<StampTaxBracket>,
96
+ pub no_amount_tax_amount: Option<u64>,
97
+ pub no_amount_rule_label: Option<String>,
98
+ }
99
+
100
+ impl StampTaxSpecialRule {
101
+ pub fn matches_date(&self, date_str: &str) -> bool {
102
+ let from_ok = match self.effective_from.as_deref() {
103
+ Some(from) => date_str >= from,
104
+ None => true,
105
+ };
106
+ let until_ok = match self.effective_until.as_deref() {
107
+ Some(until) => date_str <= until,
108
+ None => true,
109
+ };
110
+ from_ok && until_ok
111
+ }
112
+
113
+ pub fn matches_amount(&self, stated_amount: Option<u64>) -> bool {
114
+ if self.tax_amount.is_some() {
115
+ return true;
116
+ }
117
+
118
+ match stated_amount {
119
+ Some(amount) => self.brackets.iter().any(|bracket| bracket.matches(amount)),
120
+ None => self.no_amount_tax_amount.is_some(),
121
+ }
122
+ }
123
+ }
124
+
125
+ /// 文書コードごとのパラメータ。
126
+ ///
127
+ /// # 法的根拠
128
+ /// 印紙税法 別表第一
129
+ #[derive(Debug, Clone)]
130
+ pub struct StampTaxDocumentParams {
131
+ pub code: StampTaxDocumentCode,
132
+ pub label: String,
133
+ pub citation: StampTaxCitation,
134
+ pub charge_mode: StampTaxChargeMode,
135
+ pub amount_usage: StampTaxAmountUsage,
136
+ pub base_rule_label: String,
137
+ pub base_tax_amount: Option<u64>,
138
+ pub brackets: Vec<StampTaxBracket>,
139
+ pub no_amount_tax_amount: Option<u64>,
140
+ pub no_amount_rule_label: Option<String>,
141
+ pub special_rules: Vec<StampTaxSpecialRule>,
142
+ }
143
+
144
+ /// 印紙税計算に使うパラメータセット。
145
+ #[derive(Debug, Clone)]
146
+ pub struct StampTaxParams {
147
+ pub documents: BTreeMap<StampTaxDocumentCode, StampTaxDocumentParams>,
148
+ }
149
+
150
+ impl StampTaxParams {
151
+ pub(crate) fn document_params(
152
+ &self,
153
+ document_code: StampTaxDocumentCode,
154
+ ) -> Option<&StampTaxDocumentParams> {
155
+ self.documents.get(&document_code)
156
+ }
157
+ }
158
+
159
+ #[cfg(test)]
160
+ mod tests {
161
+ use super::*;
162
+
163
+ #[test]
164
+ fn charge_mode_parses() {
165
+ assert_eq!(
166
+ StampTaxChargeMode::from_str("fixed_per_year").unwrap(),
167
+ StampTaxChargeMode::FixedPerYear
168
+ );
169
+ }
170
+
171
+ #[test]
172
+ fn amount_usage_parses() {
173
+ assert_eq!(
174
+ StampTaxAmountUsage::from_str("optional").unwrap(),
175
+ StampTaxAmountUsage::Optional
176
+ );
177
+ }
178
+
179
+ #[test]
180
+ fn bracket_match_inclusive_upper_bound() {
181
+ let bracket = StampTaxBracket {
182
+ label: "100万円以下".into(),
183
+ amount_from: 100_000,
184
+ amount_to_inclusive: Some(1_000_000),
185
+ tax_amount: 200,
186
+ };
187
+ assert!(bracket.matches(1_000_000));
188
+ assert!(!bracket.matches(1_000_001));
189
+ }
190
+ }