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.
- checksums.yaml +7 -0
- data/Gemfile +9 -0
- data/README.md +109 -0
- data/Rakefile +87 -0
- data/ext/j_law_ruby/extconf.rb +34 -0
- data/lib/j_law_ruby/build_support.rb +129 -0
- data/lib/j_law_ruby/c_ffi.rb +662 -0
- data/lib/j_law_ruby.rb +532 -0
- data/rake_support/vendor_rust.rb +51 -0
- data/test/test_c_ffi_adapter.rb +46 -0
- data/test/test_consumption_tax.rb +66 -0
- data/test/test_gemspec.rb +82 -0
- data/test/test_income_tax.rb +77 -0
- data/test/test_income_tax_deductions.rb +82 -0
- data/test/test_real_estate.rb +98 -0
- data/test/test_stamp_tax.rb +68 -0
- data/test/test_withholding_tax.rb +65 -0
- data/vendor/rust/Cargo.lock +235 -0
- data/vendor/rust/Cargo.toml +12 -0
- data/vendor/rust/crates/j-law-c-ffi/Cargo.toml +20 -0
- data/vendor/rust/crates/j-law-c-ffi/j_law_c_ffi.h +493 -0
- data/vendor/rust/crates/j-law-c-ffi/src/lib.rs +1553 -0
- data/vendor/rust/crates/j-law-core/Cargo.toml +18 -0
- data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/calculator.rs +216 -0
- data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/context.rs +29 -0
- data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/mod.rs +9 -0
- data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/params.rs +24 -0
- data/vendor/rust/crates/j-law-core/src/domains/consumption_tax/policy.rs +34 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/assessment.rs +76 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/calculator.rs +222 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/context.rs +79 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/calculator.rs +167 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/context.rs +172 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/expense.rs +465 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/mod.rs +20 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/params.rs +205 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/personal.rs +324 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/deduction/types.rs +61 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/mod.rs +24 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/params.rs +109 -0
- data/vendor/rust/crates/j-law-core/src/domains/income_tax/policy.rs +103 -0
- data/vendor/rust/crates/j-law-core/src/domains/mod.rs +5 -0
- data/vendor/rust/crates/j-law-core/src/domains/real_estate/calculator.rs +197 -0
- data/vendor/rust/crates/j-law-core/src/domains/real_estate/context.rs +48 -0
- data/vendor/rust/crates/j-law-core/src/domains/real_estate/mod.rs +9 -0
- data/vendor/rust/crates/j-law-core/src/domains/real_estate/params.rs +43 -0
- data/vendor/rust/crates/j-law-core/src/domains/real_estate/policy.rs +40 -0
- data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/calculator.rs +321 -0
- data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/context.rs +408 -0
- data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/mod.rs +12 -0
- data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/params.rs +190 -0
- data/vendor/rust/crates/j-law-core/src/domains/stamp_tax/policy.rs +105 -0
- data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/calculator.rs +247 -0
- data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/context.rs +167 -0
- data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/mod.rs +9 -0
- data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/params.rs +80 -0
- data/vendor/rust/crates/j-law-core/src/domains/withholding_tax/policy.rs +49 -0
- data/vendor/rust/crates/j-law-core/src/error.rs +171 -0
- data/vendor/rust/crates/j-law-core/src/lib.rs +9 -0
- data/vendor/rust/crates/j-law-core/src/types/amount.rs +232 -0
- data/vendor/rust/crates/j-law-core/src/types/citation.rs +82 -0
- data/vendor/rust/crates/j-law-core/src/types/date.rs +280 -0
- data/vendor/rust/crates/j-law-core/src/types/mod.rs +11 -0
- data/vendor/rust/crates/j-law-core/src/types/rate.rs +219 -0
- data/vendor/rust/crates/j-law-core/src/types/rounding.rs +81 -0
- data/vendor/rust/crates/j-law-registry/Cargo.toml +15 -0
- data/vendor/rust/crates/j-law-registry/data/consumption_tax/consumption_tax.json +70 -0
- data/vendor/rust/crates/j-law-registry/data/income_tax/deductions.json +327 -0
- data/vendor/rust/crates/j-law-registry/data/income_tax/income_tax.json +352 -0
- data/vendor/rust/crates/j-law-registry/data/real_estate/brokerage_fee.json +125 -0
- data/vendor/rust/crates/j-law-registry/data/stamp_tax/stamp_tax.json +674 -0
- data/vendor/rust/crates/j-law-registry/data/withholding_tax/withholding_tax.json +70 -0
- data/vendor/rust/crates/j-law-registry/src/consumption_tax_loader.rs +325 -0
- data/vendor/rust/crates/j-law-registry/src/consumption_tax_schema.rs +49 -0
- data/vendor/rust/crates/j-law-registry/src/income_tax_deduction_loader.rs +636 -0
- data/vendor/rust/crates/j-law-registry/src/income_tax_deduction_schema.rs +111 -0
- data/vendor/rust/crates/j-law-registry/src/income_tax_loader.rs +445 -0
- data/vendor/rust/crates/j-law-registry/src/income_tax_schema.rs +44 -0
- data/vendor/rust/crates/j-law-registry/src/lib.rs +20 -0
- data/vendor/rust/crates/j-law-registry/src/loader.rs +221 -0
- data/vendor/rust/crates/j-law-registry/src/schema.rs +73 -0
- data/vendor/rust/crates/j-law-registry/src/stamp_tax_loader.rs +374 -0
- data/vendor/rust/crates/j-law-registry/src/stamp_tax_schema.rs +72 -0
- data/vendor/rust/crates/j-law-registry/src/validator.rs +204 -0
- data/vendor/rust/crates/j-law-registry/src/withholding_tax_loader.rs +310 -0
- data/vendor/rust/crates/j-law-registry/src/withholding_tax_schema.rs +61 -0
- metadata +148 -0
|
@@ -0,0 +1,1553 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
use std::os::raw::{c_char, c_int};
|
|
3
|
+
|
|
4
|
+
use j_law_core::domains::consumption_tax::{
|
|
5
|
+
calculator::calculate_consumption_tax,
|
|
6
|
+
context::{ConsumptionTaxContext, ConsumptionTaxFlag},
|
|
7
|
+
policy::StandardConsumptionTaxPolicy,
|
|
8
|
+
};
|
|
9
|
+
use j_law_core::domains::income_tax::{
|
|
10
|
+
assessment::{calculate_income_tax_assessment, IncomeTaxAssessmentContext},
|
|
11
|
+
calculator::calculate_income_tax,
|
|
12
|
+
context::{IncomeTaxContext, IncomeTaxFlag},
|
|
13
|
+
deduction::calculate_income_deductions,
|
|
14
|
+
deduction::{
|
|
15
|
+
DependentDeductionInput, DonationDeductionInput, ExpenseDeductionInput,
|
|
16
|
+
IncomeDeductionContext, IncomeDeductionInput, IncomeDeductionKind,
|
|
17
|
+
LifeInsuranceDeductionInput, MedicalDeductionInput, PersonalDeductionInput,
|
|
18
|
+
SpouseDeductionInput,
|
|
19
|
+
},
|
|
20
|
+
policy::StandardIncomeTaxPolicy,
|
|
21
|
+
};
|
|
22
|
+
use j_law_core::domains::real_estate::{
|
|
23
|
+
calculator::calculate_brokerage_fee, context::RealEstateContext, policy::StandardMlitPolicy,
|
|
24
|
+
RealEstateFlag,
|
|
25
|
+
};
|
|
26
|
+
use j_law_core::domains::stamp_tax::{
|
|
27
|
+
calculator::calculate_stamp_tax,
|
|
28
|
+
context::{StampTaxContext, StampTaxDocumentCode, StampTaxFlag},
|
|
29
|
+
policy::StandardNtaPolicy,
|
|
30
|
+
};
|
|
31
|
+
use j_law_core::domains::withholding_tax::{
|
|
32
|
+
calculator::calculate_withholding_tax,
|
|
33
|
+
context::{WithholdingTaxCategory, WithholdingTaxContext, WithholdingTaxFlag},
|
|
34
|
+
policy::StandardWithholdingTaxPolicy,
|
|
35
|
+
};
|
|
36
|
+
use j_law_core::{JLawError, LegalDate};
|
|
37
|
+
use j_law_registry::load_brokerage_fee_params;
|
|
38
|
+
use j_law_registry::load_consumption_tax_params;
|
|
39
|
+
use j_law_registry::load_income_tax_deduction_params;
|
|
40
|
+
use j_law_registry::load_income_tax_params;
|
|
41
|
+
use j_law_registry::load_stamp_tax_params;
|
|
42
|
+
use j_law_registry::load_withholding_tax_params;
|
|
43
|
+
|
|
44
|
+
// ─── 定数 ─────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/// ティア内訳の最大件数。現行法令では 3 ティアだが余裕を持たせる。
|
|
47
|
+
pub const J_LAW_MAX_TIERS: usize = 8;
|
|
48
|
+
|
|
49
|
+
/// 所得控除内訳の最大件数。
|
|
50
|
+
pub const J_LAW_MAX_DEDUCTION_LINES: usize = 8;
|
|
51
|
+
|
|
52
|
+
/// ティアラベルの最大バイト長(NUL 終端含む)。
|
|
53
|
+
pub const J_LAW_LABEL_LEN: usize = 64;
|
|
54
|
+
|
|
55
|
+
/// エラーバッファのデフォルトバイト長。Go 側のアロケーション目安。
|
|
56
|
+
pub const J_LAW_ERROR_BUF_LEN: usize = 256;
|
|
57
|
+
|
|
58
|
+
/// C FFI の互換バージョン。
|
|
59
|
+
pub const J_LAW_C_FFI_VERSION: u32 = 4;
|
|
60
|
+
/// 源泉徴収カテゴリ: 原稿料・講演料等。
|
|
61
|
+
pub const J_LAW_WITHHOLDING_TAX_CATEGORY_MANUSCRIPT_AND_LECTURE: u32 = 1;
|
|
62
|
+
/// 源泉徴収カテゴリ: 税理士等の報酬・料金。
|
|
63
|
+
pub const J_LAW_WITHHOLDING_TAX_CATEGORY_PROFESSIONAL_FEE: u32 = 2;
|
|
64
|
+
/// 源泉徴収カテゴリ: 専属契約金。
|
|
65
|
+
pub const J_LAW_WITHHOLDING_TAX_CATEGORY_EXCLUSIVE_CONTRACT_FEE: u32 = 3;
|
|
66
|
+
|
|
67
|
+
// ─── C 互換型定義 ──────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/// 1 ティアの計算内訳(C 互換)。
|
|
70
|
+
#[repr(C)]
|
|
71
|
+
pub struct JLawBreakdownStep {
|
|
72
|
+
/// ティアラベル(NUL 終端・最大 63 文字)。
|
|
73
|
+
pub label: [c_char; J_LAW_LABEL_LEN],
|
|
74
|
+
/// ティア対象金額(円)。
|
|
75
|
+
pub base_amount: u64,
|
|
76
|
+
pub rate_numer: u64,
|
|
77
|
+
pub rate_denom: u64,
|
|
78
|
+
/// ティア計算結果(円・端数切捨て済み)。
|
|
79
|
+
pub result: u64,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// 媒介報酬の計算結果(C 互換)。
|
|
83
|
+
#[repr(C)]
|
|
84
|
+
pub struct JLawBrokerageFeeResult {
|
|
85
|
+
/// 税抜合計額(円)。
|
|
86
|
+
pub total_without_tax: u64,
|
|
87
|
+
/// 税込合計額(円)。
|
|
88
|
+
pub total_with_tax: u64,
|
|
89
|
+
/// 消費税額(円)。
|
|
90
|
+
pub tax_amount: u64,
|
|
91
|
+
/// 低廉な空き家特例が適用されたか(0 = false, 1 = true)。
|
|
92
|
+
pub low_cost_special_applied: c_int,
|
|
93
|
+
/// 各ティアの計算内訳。
|
|
94
|
+
pub breakdown: [JLawBreakdownStep; J_LAW_MAX_TIERS],
|
|
95
|
+
/// breakdown の有効件数。
|
|
96
|
+
pub breakdown_len: c_int,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── 内部ユーティリティ ────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/// UTF-8 文字列を固定長 `c_char` 配列に NUL 終端付きでコピーする。
|
|
102
|
+
///
|
|
103
|
+
/// `buf.len() - 1` バイトを超える場合はその位置で切り詰める。
|
|
104
|
+
fn copy_str_to_fixed_buf(s: &str, buf: &mut [c_char; J_LAW_LABEL_LEN]) {
|
|
105
|
+
let bytes = s.as_bytes();
|
|
106
|
+
let copy_len = bytes.len().min(J_LAW_LABEL_LEN - 1);
|
|
107
|
+
for (i, &b) in bytes[..copy_len].iter().enumerate() {
|
|
108
|
+
buf[i] = b as c_char;
|
|
109
|
+
}
|
|
110
|
+
buf[copy_len] = 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// エラーメッセージを呼び出し元バッファに書き込む。
|
|
114
|
+
///
|
|
115
|
+
/// # Safety
|
|
116
|
+
/// `buf` は `buf_len` バイト以上の有効なメモリ領域を指していること。
|
|
117
|
+
unsafe fn write_error_msg(msg: &str, buf: *mut c_char, buf_len: c_int) {
|
|
118
|
+
let bytes = msg.as_bytes();
|
|
119
|
+
let copy_len = bytes.len().min((buf_len - 1) as usize);
|
|
120
|
+
for (i, &b) in bytes[..copy_len].iter().enumerate() {
|
|
121
|
+
*buf.add(i) = b as c_char;
|
|
122
|
+
}
|
|
123
|
+
*buf.add(copy_len) = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn income_deduction_kind_to_c(kind: IncomeDeductionKind) -> u32 {
|
|
127
|
+
match kind {
|
|
128
|
+
IncomeDeductionKind::Basic => 1,
|
|
129
|
+
IncomeDeductionKind::Spouse => 2,
|
|
130
|
+
IncomeDeductionKind::Dependent => 3,
|
|
131
|
+
IncomeDeductionKind::SocialInsurance => 4,
|
|
132
|
+
IncomeDeductionKind::Medical => 5,
|
|
133
|
+
IncomeDeductionKind::LifeInsurance => 6,
|
|
134
|
+
IncomeDeductionKind::Donation => 7,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn try_count_to_u16(value: u64, field: &str) -> Result<u16, JLawError> {
|
|
139
|
+
u16::try_from(value).map_err(|_| {
|
|
140
|
+
JLawError::Input(j_law_core::InputError::InvalidDeductionInput {
|
|
141
|
+
field: field.into(),
|
|
142
|
+
reason: "u16 の上限を超えています".into(),
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fn to_income_deduction_context(
|
|
148
|
+
input: &JLawIncomeDeductionInput,
|
|
149
|
+
) -> Result<IncomeDeductionContext, JLawError> {
|
|
150
|
+
let spouse = if input.has_spouse != 0 {
|
|
151
|
+
Some(SpouseDeductionInput {
|
|
152
|
+
spouse_total_income_amount: input.spouse_total_income_amount,
|
|
153
|
+
is_same_household: input.spouse_is_same_household != 0,
|
|
154
|
+
is_elderly: input.spouse_is_elderly != 0,
|
|
155
|
+
})
|
|
156
|
+
} else {
|
|
157
|
+
None
|
|
158
|
+
};
|
|
159
|
+
let medical = if input.has_medical != 0 {
|
|
160
|
+
Some(MedicalDeductionInput {
|
|
161
|
+
medical_expense_paid: input.medical_expense_paid,
|
|
162
|
+
reimbursed_amount: input.medical_reimbursed_amount,
|
|
163
|
+
})
|
|
164
|
+
} else {
|
|
165
|
+
None
|
|
166
|
+
};
|
|
167
|
+
let life_insurance = if input.has_life_insurance != 0 {
|
|
168
|
+
Some(LifeInsuranceDeductionInput {
|
|
169
|
+
new_general_paid_amount: input.life_new_general_paid_amount,
|
|
170
|
+
new_individual_pension_paid_amount: input.life_new_individual_pension_paid_amount,
|
|
171
|
+
new_care_medical_paid_amount: input.life_new_care_medical_paid_amount,
|
|
172
|
+
old_general_paid_amount: input.life_old_general_paid_amount,
|
|
173
|
+
old_individual_pension_paid_amount: input.life_old_individual_pension_paid_amount,
|
|
174
|
+
})
|
|
175
|
+
} else {
|
|
176
|
+
None
|
|
177
|
+
};
|
|
178
|
+
let donation = if input.has_donation != 0 {
|
|
179
|
+
Some(DonationDeductionInput {
|
|
180
|
+
qualified_donation_amount: input.donation_qualified_amount,
|
|
181
|
+
})
|
|
182
|
+
} else {
|
|
183
|
+
None
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
Ok(IncomeDeductionContext {
|
|
187
|
+
total_income_amount: input.total_income_amount,
|
|
188
|
+
target_date: LegalDate::new(input.year, input.month, input.day),
|
|
189
|
+
deductions: IncomeDeductionInput {
|
|
190
|
+
personal: PersonalDeductionInput {
|
|
191
|
+
spouse,
|
|
192
|
+
dependent: DependentDeductionInput {
|
|
193
|
+
general_count: try_count_to_u16(
|
|
194
|
+
input.dependent_general_count,
|
|
195
|
+
"dependent.general_count",
|
|
196
|
+
)?,
|
|
197
|
+
specific_count: try_count_to_u16(
|
|
198
|
+
input.dependent_specific_count,
|
|
199
|
+
"dependent.specific_count",
|
|
200
|
+
)?,
|
|
201
|
+
elderly_cohabiting_count: try_count_to_u16(
|
|
202
|
+
input.dependent_elderly_cohabiting_count,
|
|
203
|
+
"dependent.elderly_cohabiting_count",
|
|
204
|
+
)?,
|
|
205
|
+
elderly_other_count: try_count_to_u16(
|
|
206
|
+
input.dependent_elderly_other_count,
|
|
207
|
+
"dependent.elderly_other_count",
|
|
208
|
+
)?,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
expense: ExpenseDeductionInput {
|
|
212
|
+
social_insurance_premium_paid: input.social_insurance_premium_paid,
|
|
213
|
+
medical,
|
|
214
|
+
life_insurance,
|
|
215
|
+
donation,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn write_income_tax_breakdown(
|
|
222
|
+
breakdown: &[j_law_core::domains::income_tax::IncomeTaxStep],
|
|
223
|
+
out_breakdown: &mut [JLawIncomeTaxStep; J_LAW_MAX_TIERS],
|
|
224
|
+
) -> c_int {
|
|
225
|
+
let len = breakdown.len().min(J_LAW_MAX_TIERS);
|
|
226
|
+
for (i, step) in breakdown.iter().take(J_LAW_MAX_TIERS).enumerate() {
|
|
227
|
+
out_breakdown[i].taxable_income = step.taxable_income;
|
|
228
|
+
out_breakdown[i].rate_numer = step.rate_numer;
|
|
229
|
+
out_breakdown[i].rate_denom = step.rate_denom;
|
|
230
|
+
out_breakdown[i].deduction = step.deduction;
|
|
231
|
+
out_breakdown[i].result = step.result.as_yen();
|
|
232
|
+
copy_str_to_fixed_buf(&step.label, &mut out_breakdown[i].label);
|
|
233
|
+
}
|
|
234
|
+
len as c_int
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fn write_income_deduction_breakdown(
|
|
238
|
+
breakdown: &[j_law_core::domains::income_tax::IncomeDeductionLine],
|
|
239
|
+
out_breakdown: &mut [JLawIncomeDeductionLine; J_LAW_MAX_DEDUCTION_LINES],
|
|
240
|
+
) -> c_int {
|
|
241
|
+
let len = breakdown.len().min(J_LAW_MAX_DEDUCTION_LINES);
|
|
242
|
+
for (i, line) in breakdown.iter().take(J_LAW_MAX_DEDUCTION_LINES).enumerate() {
|
|
243
|
+
out_breakdown[i].kind = income_deduction_kind_to_c(line.kind);
|
|
244
|
+
out_breakdown[i].amount = line.amount.as_yen();
|
|
245
|
+
copy_str_to_fixed_buf(&line.label, &mut out_breakdown[i].label);
|
|
246
|
+
}
|
|
247
|
+
len as c_int
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fn stamp_tax_document_code_from_c(value: u32) -> Result<StampTaxDocumentCode, String> {
|
|
251
|
+
StampTaxDocumentCode::from_ffi_code(value)
|
|
252
|
+
.map_err(|e| format!("unsupported stamp tax document code: {e}"))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn stamp_tax_flags_from_c(value: u64) -> Result<HashSet<StampTaxFlag>, String> {
|
|
256
|
+
StampTaxFlag::from_bitmask(value).map_err(|e| e.to_string())
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn write_breakdown(
|
|
260
|
+
breakdown: &[j_law_core::domains::withholding_tax::WithholdingTaxStep],
|
|
261
|
+
out_breakdown: &mut [JLawBreakdownStep; J_LAW_MAX_TIERS],
|
|
262
|
+
) -> c_int {
|
|
263
|
+
let len = breakdown.len().min(J_LAW_MAX_TIERS);
|
|
264
|
+
for (i, step) in breakdown.iter().take(J_LAW_MAX_TIERS).enumerate() {
|
|
265
|
+
out_breakdown[i].base_amount = step.base_amount;
|
|
266
|
+
out_breakdown[i].rate_numer = step.rate_numer;
|
|
267
|
+
out_breakdown[i].rate_denom = step.rate_denom;
|
|
268
|
+
out_breakdown[i].result = step.result.as_yen();
|
|
269
|
+
copy_str_to_fixed_buf(&step.label, &mut out_breakdown[i].label);
|
|
270
|
+
}
|
|
271
|
+
len as c_int
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─── C FFI 公開関数 ────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
/// j-law-c-ffi の FFI バージョンを返す。
|
|
277
|
+
///
|
|
278
|
+
/// # 法的根拠
|
|
279
|
+
/// なし(FFI 互換確認用)
|
|
280
|
+
#[no_mangle]
|
|
281
|
+
pub extern "C" fn j_law_c_ffi_version() -> u32 {
|
|
282
|
+
J_LAW_C_FFI_VERSION
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// 宅建業法第46条に基づく媒介報酬を計算する。
|
|
286
|
+
///
|
|
287
|
+
/// # 法的根拠
|
|
288
|
+
/// 宅地建物取引業法 第46条第1項 / 国土交通省告示
|
|
289
|
+
///
|
|
290
|
+
/// # 引数
|
|
291
|
+
/// - `price`: 売買価格(円)
|
|
292
|
+
/// - `year`, `month`, `day`: 基準日
|
|
293
|
+
/// - `is_low_cost_vacant_house`: 低廉な空き家特例フラグ(0 = false, 非0 = true)
|
|
294
|
+
/// WARNING: 対象物件が「低廉な空き家」に該当するかの事実認定は呼び出し元の責任。
|
|
295
|
+
/// - `is_seller`: 売主側フラグ(0 = false, 非0 = true)
|
|
296
|
+
/// 2018年1月1日〜2024年6月30日の低廉特例は売主のみに適用される。
|
|
297
|
+
/// WARNING: 売主・買主の事実認定は呼び出し元の責任。
|
|
298
|
+
/// - `out_result`: [OUT] 計算結果の書き込み先(呼び出し元が確保すること)
|
|
299
|
+
/// - `error_buf`: [OUT] エラーメッセージの書き込み先(呼び出し元が確保すること)
|
|
300
|
+
/// - `error_buf_len`: `error_buf` のバイト長(推奨: `J_LAW_ERROR_BUF_LEN` = 256)
|
|
301
|
+
///
|
|
302
|
+
/// # 戻り値
|
|
303
|
+
/// - `0`: 成功。`out_result` にデータが書き込まれている。
|
|
304
|
+
/// - `非0`: 失敗。`error_buf` に NUL 終端エラーメッセージが書き込まれている。
|
|
305
|
+
///
|
|
306
|
+
/// # Safety
|
|
307
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
308
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
309
|
+
/// - `error_buf_len` は 1 以上であること。
|
|
310
|
+
#[no_mangle]
|
|
311
|
+
pub unsafe extern "C" fn j_law_calc_brokerage_fee(
|
|
312
|
+
price: u64,
|
|
313
|
+
year: u16,
|
|
314
|
+
month: u8,
|
|
315
|
+
day: u8,
|
|
316
|
+
is_low_cost_vacant_house: c_int,
|
|
317
|
+
is_seller: c_int,
|
|
318
|
+
out_result: *mut JLawBrokerageFeeResult,
|
|
319
|
+
error_buf: *mut c_char,
|
|
320
|
+
error_buf_len: c_int,
|
|
321
|
+
) -> c_int {
|
|
322
|
+
if out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
323
|
+
return -1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// パラメータロード
|
|
327
|
+
let params = match load_brokerage_fee_params(LegalDate::new(year, month, day)) {
|
|
328
|
+
Ok(p) => p,
|
|
329
|
+
Err(e) => {
|
|
330
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
331
|
+
return 1;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// フラグ構築
|
|
336
|
+
let mut flags = HashSet::new();
|
|
337
|
+
if is_low_cost_vacant_house != 0 {
|
|
338
|
+
flags.insert(RealEstateFlag::IsLowCostVacantHouse);
|
|
339
|
+
}
|
|
340
|
+
if is_seller != 0 {
|
|
341
|
+
flags.insert(RealEstateFlag::IsSeller);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let ctx = RealEstateContext {
|
|
345
|
+
price,
|
|
346
|
+
target_date: LegalDate::new(year, month, day),
|
|
347
|
+
flags,
|
|
348
|
+
policy: Box::new(StandardMlitPolicy),
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// 計算実行
|
|
352
|
+
let result = match calculate_brokerage_fee(&ctx, ¶ms) {
|
|
353
|
+
Ok(r) => r,
|
|
354
|
+
Err(e) => {
|
|
355
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
356
|
+
return 1;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// 結果を out_result に書き込む
|
|
361
|
+
let out = &mut *out_result;
|
|
362
|
+
out.total_without_tax = result.total_without_tax.as_yen();
|
|
363
|
+
out.total_with_tax = result.total_with_tax.as_yen();
|
|
364
|
+
out.tax_amount = result.tax_amount.as_yen();
|
|
365
|
+
out.low_cost_special_applied = if result.low_cost_special_applied {
|
|
366
|
+
1
|
|
367
|
+
} else {
|
|
368
|
+
0
|
|
369
|
+
};
|
|
370
|
+
out.breakdown_len = result.breakdown.len().min(J_LAW_MAX_TIERS) as c_int;
|
|
371
|
+
|
|
372
|
+
for (i, step) in result.breakdown.iter().take(J_LAW_MAX_TIERS).enumerate() {
|
|
373
|
+
out.breakdown[i].base_amount = step.base_amount;
|
|
374
|
+
out.breakdown[i].rate_numer = step.rate_numer;
|
|
375
|
+
out.breakdown[i].rate_denom = step.rate_denom;
|
|
376
|
+
out.breakdown[i].result = step.result.as_yen();
|
|
377
|
+
copy_str_to_fixed_buf(&step.label, &mut out.breakdown[i].label);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
0
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── 源泉徴収 C 互換型定義 ──────────────────────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
/// 源泉徴収税額の計算結果(C 互換)。
|
|
386
|
+
#[repr(C)]
|
|
387
|
+
pub struct JLawWithholdingTaxResult {
|
|
388
|
+
/// 支払総額(円)。
|
|
389
|
+
pub gross_payment_amount: u64,
|
|
390
|
+
/// 源泉徴収税額の計算対象額(円)。
|
|
391
|
+
pub taxable_payment_amount: u64,
|
|
392
|
+
/// 源泉徴収税額(円)。
|
|
393
|
+
pub tax_amount: u64,
|
|
394
|
+
/// 源泉徴収後の支払額(円)。
|
|
395
|
+
pub net_payment_amount: u64,
|
|
396
|
+
/// カテゴリコード。
|
|
397
|
+
pub category: u32,
|
|
398
|
+
/// 応募作品等の入選賞金・謝金の非課税特例を適用したか。
|
|
399
|
+
pub submission_prize_exempted: c_int,
|
|
400
|
+
/// 計算内訳。
|
|
401
|
+
pub breakdown: [JLawBreakdownStep; J_LAW_MAX_TIERS],
|
|
402
|
+
/// breakdown の有効件数。
|
|
403
|
+
pub breakdown_len: c_int,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// 所得税法第204条第1項に基づく報酬・料金等の源泉徴収税額を計算する。
|
|
407
|
+
///
|
|
408
|
+
/// # 法的根拠
|
|
409
|
+
/// 所得税法 第204条第1項 / 国税庁タックスアンサー No.2795 / No.2798 / No.2810
|
|
410
|
+
///
|
|
411
|
+
/// # 引数
|
|
412
|
+
/// - `payment_amount`: 支払総額(円)
|
|
413
|
+
/// - `separated_consumption_tax_amount`: 区分表示された消費税額(円)
|
|
414
|
+
/// - `year`, `month`, `day`: 基準日
|
|
415
|
+
/// - `category`: カテゴリコード(`J_LAW_WITHHOLDING_TAX_CATEGORY_*`)
|
|
416
|
+
/// - `is_submission_prize`: 応募作品等の入選賞金・謝金として扱うか
|
|
417
|
+
/// - `out_result`: [OUT] 計算結果の書き込み先
|
|
418
|
+
/// - `error_buf`: [OUT] エラーメッセージの書き込み先
|
|
419
|
+
/// - `error_buf_len`: `error_buf` のバイト長
|
|
420
|
+
///
|
|
421
|
+
/// # Safety
|
|
422
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
423
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
424
|
+
#[no_mangle]
|
|
425
|
+
pub unsafe extern "C" fn j_law_calc_withholding_tax(
|
|
426
|
+
payment_amount: u64,
|
|
427
|
+
separated_consumption_tax_amount: u64,
|
|
428
|
+
year: u16,
|
|
429
|
+
month: u8,
|
|
430
|
+
day: u8,
|
|
431
|
+
category: u32,
|
|
432
|
+
is_submission_prize: c_int,
|
|
433
|
+
out_result: *mut JLawWithholdingTaxResult,
|
|
434
|
+
error_buf: *mut c_char,
|
|
435
|
+
error_buf_len: c_int,
|
|
436
|
+
) -> c_int {
|
|
437
|
+
if out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
438
|
+
return -1;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let category = match WithholdingTaxCategory::from_ffi_code(category) {
|
|
442
|
+
Ok(category) => category,
|
|
443
|
+
Err(e) => {
|
|
444
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
445
|
+
return 1;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
let params = match load_withholding_tax_params(LegalDate::new(year, month, day)) {
|
|
450
|
+
Ok(params) => params,
|
|
451
|
+
Err(e) => {
|
|
452
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
453
|
+
return 1;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
let mut flags = HashSet::new();
|
|
458
|
+
if is_submission_prize != 0 {
|
|
459
|
+
flags.insert(WithholdingTaxFlag::IsSubmissionPrize);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let ctx = WithholdingTaxContext {
|
|
463
|
+
payment_amount,
|
|
464
|
+
separated_consumption_tax_amount,
|
|
465
|
+
category,
|
|
466
|
+
target_date: LegalDate::new(year, month, day),
|
|
467
|
+
flags,
|
|
468
|
+
policy: Box::new(StandardWithholdingTaxPolicy),
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
let result = match calculate_withholding_tax(&ctx, ¶ms) {
|
|
472
|
+
Ok(result) => result,
|
|
473
|
+
Err(e) => {
|
|
474
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
475
|
+
return 1;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
let out = &mut *out_result;
|
|
480
|
+
out.gross_payment_amount = result.gross_payment_amount.as_yen();
|
|
481
|
+
out.taxable_payment_amount = result.taxable_payment_amount.as_yen();
|
|
482
|
+
out.tax_amount = result.tax_amount.as_yen();
|
|
483
|
+
out.net_payment_amount = result.net_payment_amount.as_yen();
|
|
484
|
+
out.category = u32::from(result.category);
|
|
485
|
+
out.submission_prize_exempted = if result.submission_prize_exempted {
|
|
486
|
+
1
|
|
487
|
+
} else {
|
|
488
|
+
0
|
|
489
|
+
};
|
|
490
|
+
out.breakdown_len = write_breakdown(&result.breakdown, &mut out.breakdown);
|
|
491
|
+
|
|
492
|
+
0
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ─── 所得税 C 互換型定義 ────────────────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
/// 所得税の計算内訳(速算表の適用結果・C 互換)。
|
|
498
|
+
#[repr(C)]
|
|
499
|
+
pub struct JLawIncomeTaxStep {
|
|
500
|
+
/// ラベル(NUL 終端・最大 63 文字)。
|
|
501
|
+
pub label: [c_char; J_LAW_LABEL_LEN],
|
|
502
|
+
/// 課税所得金額(円)。
|
|
503
|
+
pub taxable_income: u64,
|
|
504
|
+
pub rate_numer: u64,
|
|
505
|
+
pub rate_denom: u64,
|
|
506
|
+
/// 速算表の控除額(円)。
|
|
507
|
+
pub deduction: u64,
|
|
508
|
+
/// 算出税額(円)。
|
|
509
|
+
pub result: u64,
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/// 所得税の計算結果(C 互換)。
|
|
513
|
+
#[repr(C)]
|
|
514
|
+
pub struct JLawIncomeTaxResult {
|
|
515
|
+
/// 基準所得税額(円)。
|
|
516
|
+
pub base_tax: u64,
|
|
517
|
+
/// 復興特別所得税額(円)。
|
|
518
|
+
pub reconstruction_tax: u64,
|
|
519
|
+
/// 申告納税額(円・100円未満切り捨て)。
|
|
520
|
+
pub total_tax: u64,
|
|
521
|
+
/// 復興特別所得税が適用されたか(0 = false, 1 = true)。
|
|
522
|
+
pub reconstruction_tax_applied: c_int,
|
|
523
|
+
/// 計算内訳。
|
|
524
|
+
pub breakdown: [JLawIncomeTaxStep; J_LAW_MAX_TIERS],
|
|
525
|
+
/// breakdown の有効件数。
|
|
526
|
+
pub breakdown_len: c_int,
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/// 所得控除の内訳1行(C 互換)。
|
|
530
|
+
#[repr(C)]
|
|
531
|
+
pub struct JLawIncomeDeductionLine {
|
|
532
|
+
/// 控除種別定数。
|
|
533
|
+
pub kind: u32,
|
|
534
|
+
/// ラベル(NUL 終端・最大 63 文字)。
|
|
535
|
+
pub label: [c_char; J_LAW_LABEL_LEN],
|
|
536
|
+
/// 控除額(円)。
|
|
537
|
+
pub amount: u64,
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/// 所得控除計算の入力(C 互換)。
|
|
541
|
+
#[repr(C)]
|
|
542
|
+
pub struct JLawIncomeDeductionInput {
|
|
543
|
+
/// 総所得金額等(円)。
|
|
544
|
+
pub total_income_amount: u64,
|
|
545
|
+
/// 基準日(年)。
|
|
546
|
+
pub year: u16,
|
|
547
|
+
/// 基準日(月)。
|
|
548
|
+
pub month: u8,
|
|
549
|
+
/// 基準日(日)。
|
|
550
|
+
pub day: u8,
|
|
551
|
+
/// 配偶者控除入力があるか。
|
|
552
|
+
pub has_spouse: c_int,
|
|
553
|
+
/// 配偶者の合計所得金額(円)。
|
|
554
|
+
pub spouse_total_income_amount: u64,
|
|
555
|
+
/// 配偶者が生計を一にするか。
|
|
556
|
+
pub spouse_is_same_household: c_int,
|
|
557
|
+
/// 配偶者が老人控除対象配偶者か。
|
|
558
|
+
pub spouse_is_elderly: c_int,
|
|
559
|
+
/// 一般の控除対象扶養親族の人数。
|
|
560
|
+
pub dependent_general_count: u64,
|
|
561
|
+
/// 特定扶養親族の人数。
|
|
562
|
+
pub dependent_specific_count: u64,
|
|
563
|
+
/// 同居老親等の人数。
|
|
564
|
+
pub dependent_elderly_cohabiting_count: u64,
|
|
565
|
+
/// 同居老親等以外の老人扶養親族の人数。
|
|
566
|
+
pub dependent_elderly_other_count: u64,
|
|
567
|
+
/// 社会保険料控除の対象支払額(円)。
|
|
568
|
+
pub social_insurance_premium_paid: u64,
|
|
569
|
+
/// 医療費控除入力があるか。
|
|
570
|
+
pub has_medical: c_int,
|
|
571
|
+
/// 支払医療費(円)。
|
|
572
|
+
pub medical_expense_paid: u64,
|
|
573
|
+
/// 補填額(円)。
|
|
574
|
+
pub medical_reimbursed_amount: u64,
|
|
575
|
+
/// 生命保険料控除入力があるか。
|
|
576
|
+
pub has_life_insurance: c_int,
|
|
577
|
+
/// 新契約の一般生命保険料(円)。
|
|
578
|
+
pub life_new_general_paid_amount: u64,
|
|
579
|
+
/// 新契約の個人年金保険料(円)。
|
|
580
|
+
pub life_new_individual_pension_paid_amount: u64,
|
|
581
|
+
/// 新契約の介護医療保険料(円)。
|
|
582
|
+
pub life_new_care_medical_paid_amount: u64,
|
|
583
|
+
/// 旧契約の一般生命保険料(円)。
|
|
584
|
+
pub life_old_general_paid_amount: u64,
|
|
585
|
+
/// 旧契約の個人年金保険料(円)。
|
|
586
|
+
pub life_old_individual_pension_paid_amount: u64,
|
|
587
|
+
/// 寄附金控除入力があるか。
|
|
588
|
+
pub has_donation: c_int,
|
|
589
|
+
/// 控除対象寄附金額(円)。
|
|
590
|
+
pub donation_qualified_amount: u64,
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/// 所得控除の計算結果(C 互換)。
|
|
594
|
+
#[repr(C)]
|
|
595
|
+
pub struct JLawIncomeDeductionResult {
|
|
596
|
+
/// 総所得金額等(円)。
|
|
597
|
+
pub total_income_amount: u64,
|
|
598
|
+
/// 所得控除額合計(円)。
|
|
599
|
+
pub total_deductions: u64,
|
|
600
|
+
/// 1,000円未満切り捨て前の課税所得金額(円)。
|
|
601
|
+
pub taxable_income_before_truncation: u64,
|
|
602
|
+
/// 1,000円未満切り捨て後の課税所得金額(円)。
|
|
603
|
+
pub taxable_income: u64,
|
|
604
|
+
/// 控除内訳。
|
|
605
|
+
pub breakdown: [JLawIncomeDeductionLine; J_LAW_MAX_DEDUCTION_LINES],
|
|
606
|
+
/// breakdown の有効件数。
|
|
607
|
+
pub breakdown_len: c_int,
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/// 所得控除から所得税額までの通し計算結果(C 互換)。
|
|
611
|
+
#[repr(C)]
|
|
612
|
+
pub struct JLawIncomeTaxAssessmentResult {
|
|
613
|
+
/// 総所得金額等(円)。
|
|
614
|
+
pub total_income_amount: u64,
|
|
615
|
+
/// 所得控除額合計(円)。
|
|
616
|
+
pub total_deductions: u64,
|
|
617
|
+
/// 1,000円未満切り捨て前の課税所得金額(円)。
|
|
618
|
+
pub taxable_income_before_truncation: u64,
|
|
619
|
+
/// 1,000円未満切り捨て後の課税所得金額(円)。
|
|
620
|
+
pub taxable_income: u64,
|
|
621
|
+
/// 基準所得税額(円)。
|
|
622
|
+
pub base_tax: u64,
|
|
623
|
+
/// 復興特別所得税額(円)。
|
|
624
|
+
pub reconstruction_tax: u64,
|
|
625
|
+
/// 申告納税額(円)。
|
|
626
|
+
pub total_tax: u64,
|
|
627
|
+
/// 復興特別所得税が適用されたか。
|
|
628
|
+
pub reconstruction_tax_applied: c_int,
|
|
629
|
+
/// 所得控除の内訳。
|
|
630
|
+
pub deduction_breakdown: [JLawIncomeDeductionLine; J_LAW_MAX_DEDUCTION_LINES],
|
|
631
|
+
/// deduction_breakdown の有効件数。
|
|
632
|
+
pub deduction_breakdown_len: c_int,
|
|
633
|
+
/// 所得税の内訳。
|
|
634
|
+
pub tax_breakdown: [JLawIncomeTaxStep; J_LAW_MAX_TIERS],
|
|
635
|
+
/// tax_breakdown の有効件数。
|
|
636
|
+
pub tax_breakdown_len: c_int,
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ─── 所得税 C FFI 公開関数 ──────────────────────────────────────────────────────
|
|
640
|
+
|
|
641
|
+
/// 所得税法第89条に基づく所得税額を計算する。
|
|
642
|
+
///
|
|
643
|
+
/// # 法的根拠
|
|
644
|
+
/// 所得税法 第89条第1項 / 復興財源確保法 第13条
|
|
645
|
+
///
|
|
646
|
+
/// # 引数
|
|
647
|
+
/// - `taxable_income`: 課税所得金額(円)
|
|
648
|
+
/// - `year`, `month`, `day`: 基準日
|
|
649
|
+
/// - `apply_reconstruction_tax`: 復興特別所得税を適用するか(0 = false, 非0 = true)
|
|
650
|
+
/// - `out_result`: [OUT] 計算結果の書き込み先(呼び出し元が確保すること)
|
|
651
|
+
/// - `error_buf`: [OUT] エラーメッセージの書き込み先(呼び出し元が確保すること)
|
|
652
|
+
/// - `error_buf_len`: `error_buf` のバイト長(推奨: `J_LAW_ERROR_BUF_LEN` = 256)
|
|
653
|
+
///
|
|
654
|
+
/// # 戻り値
|
|
655
|
+
/// - `0`: 成功。`out_result` にデータが書き込まれている。
|
|
656
|
+
/// - `非0`: 失敗。`error_buf` に NUL 終端エラーメッセージが書き込まれている。
|
|
657
|
+
///
|
|
658
|
+
/// # Safety
|
|
659
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
660
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
661
|
+
/// - `error_buf_len` は 1 以上であること。
|
|
662
|
+
#[no_mangle]
|
|
663
|
+
pub unsafe extern "C" fn j_law_calc_income_tax(
|
|
664
|
+
taxable_income: u64,
|
|
665
|
+
year: u16,
|
|
666
|
+
month: u8,
|
|
667
|
+
day: u8,
|
|
668
|
+
apply_reconstruction_tax: c_int,
|
|
669
|
+
out_result: *mut JLawIncomeTaxResult,
|
|
670
|
+
error_buf: *mut c_char,
|
|
671
|
+
error_buf_len: c_int,
|
|
672
|
+
) -> c_int {
|
|
673
|
+
if out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
674
|
+
return -1;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// パラメータロード
|
|
678
|
+
let params = match load_income_tax_params(LegalDate::new(year, month, day)) {
|
|
679
|
+
Ok(p) => p,
|
|
680
|
+
Err(e) => {
|
|
681
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
682
|
+
return 1;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// フラグ構築
|
|
687
|
+
let mut flags = HashSet::new();
|
|
688
|
+
if apply_reconstruction_tax != 0 {
|
|
689
|
+
flags.insert(IncomeTaxFlag::ApplyReconstructionTax);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
let ctx = IncomeTaxContext {
|
|
693
|
+
taxable_income,
|
|
694
|
+
target_date: LegalDate::new(year, month, day),
|
|
695
|
+
flags,
|
|
696
|
+
policy: Box::new(StandardIncomeTaxPolicy),
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// 計算実行
|
|
700
|
+
let result = match calculate_income_tax(&ctx, ¶ms) {
|
|
701
|
+
Ok(r) => r,
|
|
702
|
+
Err(e) => {
|
|
703
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
704
|
+
return 1;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// 結果を out_result に書き込む
|
|
709
|
+
let out = &mut *out_result;
|
|
710
|
+
out.base_tax = result.base_tax.as_yen();
|
|
711
|
+
out.reconstruction_tax = result.reconstruction_tax.as_yen();
|
|
712
|
+
out.total_tax = result.total_tax.as_yen();
|
|
713
|
+
out.reconstruction_tax_applied = if result.reconstruction_tax_applied {
|
|
714
|
+
1
|
|
715
|
+
} else {
|
|
716
|
+
0
|
|
717
|
+
};
|
|
718
|
+
out.breakdown_len = write_income_tax_breakdown(&result.breakdown, &mut out.breakdown);
|
|
719
|
+
|
|
720
|
+
0
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/// 所得控除を計算し、課税所得金額までを返す。
|
|
724
|
+
///
|
|
725
|
+
/// # 法的根拠
|
|
726
|
+
/// 所得税法 第73条(医療費控除)
|
|
727
|
+
/// 所得税法 第74条(社会保険料控除)
|
|
728
|
+
/// 所得税法 第76条(生命保険料控除)
|
|
729
|
+
/// 所得税法 第78条(寄附金控除)
|
|
730
|
+
/// 所得税法 第83条(配偶者控除)
|
|
731
|
+
/// 所得税法 第84条(扶養控除)
|
|
732
|
+
/// 所得税法 第86条(基礎控除)
|
|
733
|
+
///
|
|
734
|
+
/// # Safety
|
|
735
|
+
/// - `input` は有効な入力ポインタであること。
|
|
736
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
737
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
738
|
+
#[no_mangle]
|
|
739
|
+
pub unsafe extern "C" fn j_law_calc_income_deductions(
|
|
740
|
+
input: *const JLawIncomeDeductionInput,
|
|
741
|
+
out_result: *mut JLawIncomeDeductionResult,
|
|
742
|
+
error_buf: *mut c_char,
|
|
743
|
+
error_buf_len: c_int,
|
|
744
|
+
) -> c_int {
|
|
745
|
+
if input.is_null() || out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
746
|
+
return -1;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
let input = &*input;
|
|
750
|
+
let ctx = match to_income_deduction_context(input) {
|
|
751
|
+
Ok(ctx) => ctx,
|
|
752
|
+
Err(e) => {
|
|
753
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
754
|
+
return 1;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
let params = match load_income_tax_deduction_params(LegalDate::new(
|
|
758
|
+
input.year,
|
|
759
|
+
input.month,
|
|
760
|
+
input.day,
|
|
761
|
+
)) {
|
|
762
|
+
Ok(params) => params,
|
|
763
|
+
Err(e) => {
|
|
764
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
765
|
+
return 1;
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
let result = match calculate_income_deductions(&ctx, ¶ms) {
|
|
769
|
+
Ok(result) => result,
|
|
770
|
+
Err(e) => {
|
|
771
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
772
|
+
return 1;
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
let out = &mut *out_result;
|
|
777
|
+
out.total_income_amount = result.total_income_amount.as_yen();
|
|
778
|
+
out.total_deductions = result.total_deductions.as_yen();
|
|
779
|
+
out.taxable_income_before_truncation = result.taxable_income_before_truncation.as_yen();
|
|
780
|
+
out.taxable_income = result.taxable_income.as_yen();
|
|
781
|
+
out.breakdown_len = write_income_deduction_breakdown(&result.breakdown, &mut out.breakdown);
|
|
782
|
+
|
|
783
|
+
0
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/// 所得控除の計算から所得税額までを通しで計算する。
|
|
787
|
+
///
|
|
788
|
+
/// # 法的根拠
|
|
789
|
+
/// 所得税法 第73条(医療費控除)
|
|
790
|
+
/// 所得税法 第74条(社会保険料控除)
|
|
791
|
+
/// 所得税法 第76条(生命保険料控除)
|
|
792
|
+
/// 所得税法 第78条(寄附金控除)
|
|
793
|
+
/// 所得税法 第83条(配偶者控除)
|
|
794
|
+
/// 所得税法 第84条(扶養控除)
|
|
795
|
+
/// 所得税法 第86条(基礎控除)
|
|
796
|
+
/// 所得税法 第89条第1項(所得税の税率)
|
|
797
|
+
/// 復興財源確保法 第13条(復興特別所得税)
|
|
798
|
+
///
|
|
799
|
+
/// # Safety
|
|
800
|
+
/// - `input` は有効な入力ポインタであること。
|
|
801
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
802
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
803
|
+
#[no_mangle]
|
|
804
|
+
pub unsafe extern "C" fn j_law_calc_income_tax_assessment(
|
|
805
|
+
input: *const JLawIncomeDeductionInput,
|
|
806
|
+
apply_reconstruction_tax: c_int,
|
|
807
|
+
out_result: *mut JLawIncomeTaxAssessmentResult,
|
|
808
|
+
error_buf: *mut c_char,
|
|
809
|
+
error_buf_len: c_int,
|
|
810
|
+
) -> c_int {
|
|
811
|
+
if input.is_null() || out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
812
|
+
return -1;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
let input = &*input;
|
|
816
|
+
let deduction_context = match to_income_deduction_context(input) {
|
|
817
|
+
Ok(ctx) => ctx,
|
|
818
|
+
Err(e) => {
|
|
819
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
820
|
+
return 1;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
let deduction_params = match load_income_tax_deduction_params(LegalDate::new(
|
|
824
|
+
input.year,
|
|
825
|
+
input.month,
|
|
826
|
+
input.day,
|
|
827
|
+
)) {
|
|
828
|
+
Ok(params) => params,
|
|
829
|
+
Err(e) => {
|
|
830
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
831
|
+
return 1;
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
let tax_params =
|
|
835
|
+
match load_income_tax_params(LegalDate::new(input.year, input.month, input.day)) {
|
|
836
|
+
Ok(params) => params,
|
|
837
|
+
Err(e) => {
|
|
838
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
839
|
+
return 1;
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
let mut flags = HashSet::new();
|
|
844
|
+
if apply_reconstruction_tax != 0 {
|
|
845
|
+
flags.insert(IncomeTaxFlag::ApplyReconstructionTax);
|
|
846
|
+
}
|
|
847
|
+
let ctx = IncomeTaxAssessmentContext {
|
|
848
|
+
deduction_context,
|
|
849
|
+
flags,
|
|
850
|
+
policy: Box::new(StandardIncomeTaxPolicy),
|
|
851
|
+
};
|
|
852
|
+
let result = match calculate_income_tax_assessment(&ctx, &deduction_params, &tax_params) {
|
|
853
|
+
Ok(result) => result,
|
|
854
|
+
Err(e) => {
|
|
855
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
856
|
+
return 1;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
let out = &mut *out_result;
|
|
861
|
+
out.total_income_amount = result.deductions.total_income_amount.as_yen();
|
|
862
|
+
out.total_deductions = result.deductions.total_deductions.as_yen();
|
|
863
|
+
out.taxable_income_before_truncation =
|
|
864
|
+
result.deductions.taxable_income_before_truncation.as_yen();
|
|
865
|
+
out.taxable_income = result.deductions.taxable_income.as_yen();
|
|
866
|
+
out.base_tax = result.tax.base_tax.as_yen();
|
|
867
|
+
out.reconstruction_tax = result.tax.reconstruction_tax.as_yen();
|
|
868
|
+
out.total_tax = result.tax.total_tax.as_yen();
|
|
869
|
+
out.reconstruction_tax_applied = if result.tax.reconstruction_tax_applied {
|
|
870
|
+
1
|
|
871
|
+
} else {
|
|
872
|
+
0
|
|
873
|
+
};
|
|
874
|
+
out.deduction_breakdown_len = write_income_deduction_breakdown(
|
|
875
|
+
&result.deductions.breakdown,
|
|
876
|
+
&mut out.deduction_breakdown,
|
|
877
|
+
);
|
|
878
|
+
out.tax_breakdown_len =
|
|
879
|
+
write_income_tax_breakdown(&result.tax.breakdown, &mut out.tax_breakdown);
|
|
880
|
+
|
|
881
|
+
0
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ─── 消費税 C 互換型定義 ────────────────────────────────────────────────────────
|
|
885
|
+
|
|
886
|
+
/// 消費税の計算結果(C 互換)。
|
|
887
|
+
#[repr(C)]
|
|
888
|
+
pub struct JLawConsumptionTaxResult {
|
|
889
|
+
/// 消費税額(円)。
|
|
890
|
+
pub tax_amount: u64,
|
|
891
|
+
/// 税込金額(円)。
|
|
892
|
+
pub amount_with_tax: u64,
|
|
893
|
+
/// 税抜金額(円)。
|
|
894
|
+
pub amount_without_tax: u64,
|
|
895
|
+
/// 適用税率の分子。
|
|
896
|
+
pub applied_rate_numer: u64,
|
|
897
|
+
/// 適用税率の分母。
|
|
898
|
+
pub applied_rate_denom: u64,
|
|
899
|
+
/// 軽減税率が適用されたか(0 = false, 1 = true)。
|
|
900
|
+
pub is_reduced_rate: c_int,
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// ─── 消費税 C FFI 公開関数 ──────────────────────────────────────────────────────
|
|
904
|
+
|
|
905
|
+
/// 消費税法第29条に基づく消費税額を計算する。
|
|
906
|
+
///
|
|
907
|
+
/// # 法的根拠
|
|
908
|
+
/// 消費税法 第29条(税率)
|
|
909
|
+
///
|
|
910
|
+
/// # 引数
|
|
911
|
+
/// - `amount`: 課税標準額(税抜き・円)
|
|
912
|
+
/// - `year`, `month`, `day`: 基準日
|
|
913
|
+
/// - `is_reduced_rate`: 軽減税率フラグ(0 = false, 非0 = true)
|
|
914
|
+
/// 2019-10-01以降の飲食料品・新聞等に適用される8%軽減税率。
|
|
915
|
+
/// WARNING: 事実認定は呼び出し元の責任。
|
|
916
|
+
/// - `out_result`: [OUT] 計算結果の書き込み先(呼び出し元が確保すること)
|
|
917
|
+
/// - `error_buf`: [OUT] エラーメッセージの書き込み先(呼び出し元が確保すること)
|
|
918
|
+
/// - `error_buf_len`: `error_buf` のバイト長(推奨: `J_LAW_ERROR_BUF_LEN` = 256)
|
|
919
|
+
///
|
|
920
|
+
/// # 戻り値
|
|
921
|
+
/// - `0`: 成功。`out_result` にデータが書き込まれている。
|
|
922
|
+
/// - `非0`: 失敗。`error_buf` に NUL 終端エラーメッセージが書き込まれている。
|
|
923
|
+
///
|
|
924
|
+
/// # Safety
|
|
925
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
926
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
927
|
+
/// - `error_buf_len` は 1 以上であること。
|
|
928
|
+
#[no_mangle]
|
|
929
|
+
pub unsafe extern "C" fn j_law_calc_consumption_tax(
|
|
930
|
+
amount: u64,
|
|
931
|
+
year: u16,
|
|
932
|
+
month: u8,
|
|
933
|
+
day: u8,
|
|
934
|
+
is_reduced_rate: c_int,
|
|
935
|
+
out_result: *mut JLawConsumptionTaxResult,
|
|
936
|
+
error_buf: *mut c_char,
|
|
937
|
+
error_buf_len: c_int,
|
|
938
|
+
) -> c_int {
|
|
939
|
+
if out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
940
|
+
return -1;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// パラメータロード
|
|
944
|
+
let params = match load_consumption_tax_params(LegalDate::new(year, month, day)) {
|
|
945
|
+
Ok(p) => p,
|
|
946
|
+
Err(e) => {
|
|
947
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
948
|
+
return 1;
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// フラグ構築
|
|
953
|
+
let mut flags = HashSet::new();
|
|
954
|
+
if is_reduced_rate != 0 {
|
|
955
|
+
flags.insert(ConsumptionTaxFlag::ReducedRate);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
let ctx = ConsumptionTaxContext {
|
|
959
|
+
amount,
|
|
960
|
+
target_date: LegalDate::new(year, month, day),
|
|
961
|
+
flags,
|
|
962
|
+
policy: Box::new(StandardConsumptionTaxPolicy),
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// 計算実行
|
|
966
|
+
let result = match calculate_consumption_tax(&ctx, ¶ms) {
|
|
967
|
+
Ok(r) => r,
|
|
968
|
+
Err(e) => {
|
|
969
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
970
|
+
return 1;
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
// 結果を out_result に書き込む
|
|
975
|
+
let out = &mut *out_result;
|
|
976
|
+
out.tax_amount = result.tax_amount.as_yen();
|
|
977
|
+
out.amount_with_tax = result.amount_with_tax.as_yen();
|
|
978
|
+
out.amount_without_tax = result.amount_without_tax.as_yen();
|
|
979
|
+
out.applied_rate_numer = result.applied_rate_numer;
|
|
980
|
+
out.applied_rate_denom = result.applied_rate_denom;
|
|
981
|
+
out.is_reduced_rate = if result.is_reduced_rate { 1 } else { 0 };
|
|
982
|
+
|
|
983
|
+
0
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// ─── 印紙税 C 互換型定義 ────────────────────────────────────────────────────────
|
|
987
|
+
|
|
988
|
+
/// 印紙税の計算結果(C 互換)。
|
|
989
|
+
#[repr(C)]
|
|
990
|
+
pub struct JLawStampTaxResult {
|
|
991
|
+
/// 印紙税額(円)。
|
|
992
|
+
pub tax_amount: u64,
|
|
993
|
+
/// 適用された税額ルールの表示名(NUL 終端・最大 63 文字)。
|
|
994
|
+
pub rule_label: [c_char; J_LAW_LABEL_LEN],
|
|
995
|
+
/// 適用された特例ルールコード(NUL 終端・最大 63 文字)。未適用時は空文字。
|
|
996
|
+
pub applied_special_rule: [c_char; J_LAW_LABEL_LEN],
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// ─── 印紙税 C FFI 公開関数 ──────────────────────────────────────────────────────
|
|
1000
|
+
|
|
1001
|
+
/// 印紙税法 別表第一に基づく印紙税額を計算する。
|
|
1002
|
+
///
|
|
1003
|
+
/// # 法的根拠
|
|
1004
|
+
/// 印紙税法 別表第一 / 租税特別措置法 第91条
|
|
1005
|
+
///
|
|
1006
|
+
/// # 引数
|
|
1007
|
+
/// - `document_code`: 文書コード(`StampTaxDocumentCode::ffi_code()` の値)
|
|
1008
|
+
/// - `stated_amount`: 記載金額、受取金額、券面金額など
|
|
1009
|
+
/// - `has_stated_amount`: 記載金額を指定する場合は非0、未記載文書なら 0
|
|
1010
|
+
/// - `year`, `month`, `day`: 契約書作成日
|
|
1011
|
+
/// - `flags_bitset`: `StampTaxFlag::bitmask()` の OR
|
|
1012
|
+
/// - `out_result`: [OUT] 計算結果の書き込み先(呼び出し元が確保すること)
|
|
1013
|
+
/// - `error_buf`: [OUT] エラーメッセージの書き込み先(呼び出し元が確保すること)
|
|
1014
|
+
/// - `error_buf_len`: `error_buf` のバイト長(推奨: `J_LAW_ERROR_BUF_LEN` = 256)
|
|
1015
|
+
///
|
|
1016
|
+
/// # 戻り値
|
|
1017
|
+
/// - `0`: 成功。`out_result` にデータが書き込まれている。
|
|
1018
|
+
/// - `非0`: 失敗。`error_buf` に NUL 終端エラーメッセージが書き込まれている。
|
|
1019
|
+
///
|
|
1020
|
+
/// # Safety
|
|
1021
|
+
/// - `out_result` は呼び出し元が所有する有効なポインタであること。
|
|
1022
|
+
/// - `error_buf` は `error_buf_len` バイト以上の領域を指していること。
|
|
1023
|
+
/// - `error_buf_len` は 1 以上であること。
|
|
1024
|
+
#[no_mangle]
|
|
1025
|
+
pub unsafe extern "C" fn j_law_calc_stamp_tax(
|
|
1026
|
+
document_code: u32,
|
|
1027
|
+
stated_amount: u64,
|
|
1028
|
+
has_stated_amount: c_int,
|
|
1029
|
+
year: u16,
|
|
1030
|
+
month: u8,
|
|
1031
|
+
day: u8,
|
|
1032
|
+
flags_bitset: u64,
|
|
1033
|
+
out_result: *mut JLawStampTaxResult,
|
|
1034
|
+
error_buf: *mut c_char,
|
|
1035
|
+
error_buf_len: c_int,
|
|
1036
|
+
) -> c_int {
|
|
1037
|
+
if out_result.is_null() || error_buf.is_null() || error_buf_len <= 0 {
|
|
1038
|
+
return -1;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
let document_code = match stamp_tax_document_code_from_c(document_code) {
|
|
1042
|
+
Ok(code) => code,
|
|
1043
|
+
Err(msg) => {
|
|
1044
|
+
write_error_msg(&msg, error_buf, error_buf_len);
|
|
1045
|
+
return 1;
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
let flags = match stamp_tax_flags_from_c(flags_bitset) {
|
|
1050
|
+
Ok(flags) => flags,
|
|
1051
|
+
Err(msg) => {
|
|
1052
|
+
write_error_msg(&msg, error_buf, error_buf_len);
|
|
1053
|
+
return 1;
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
let params = match load_stamp_tax_params(LegalDate::new(year, month, day)) {
|
|
1058
|
+
Ok(p) => p,
|
|
1059
|
+
Err(e) => {
|
|
1060
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
1061
|
+
return 1;
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
let ctx = StampTaxContext {
|
|
1066
|
+
document_code,
|
|
1067
|
+
stated_amount: if has_stated_amount != 0 {
|
|
1068
|
+
Some(stated_amount)
|
|
1069
|
+
} else {
|
|
1070
|
+
None
|
|
1071
|
+
},
|
|
1072
|
+
target_date: LegalDate::new(year, month, day),
|
|
1073
|
+
flags,
|
|
1074
|
+
policy: Box::new(StandardNtaPolicy),
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
// 計算実行
|
|
1078
|
+
let result = match calculate_stamp_tax(&ctx, ¶ms) {
|
|
1079
|
+
Ok(r) => r,
|
|
1080
|
+
Err(e) => {
|
|
1081
|
+
write_error_msg(&e.to_string(), error_buf, error_buf_len);
|
|
1082
|
+
return 1;
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
let out = &mut *out_result;
|
|
1087
|
+
out.tax_amount = result.tax_amount.as_yen();
|
|
1088
|
+
copy_str_to_fixed_buf(&result.rule_label, &mut out.rule_label);
|
|
1089
|
+
copy_str_to_fixed_buf(
|
|
1090
|
+
result.applied_special_rule.as_deref().unwrap_or(""),
|
|
1091
|
+
&mut out.applied_special_rule,
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
0
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
#[cfg(test)]
|
|
1098
|
+
mod tests {
|
|
1099
|
+
use super::*;
|
|
1100
|
+
|
|
1101
|
+
fn fixed_buf_to_string(buf: &[c_char; J_LAW_LABEL_LEN]) -> String {
|
|
1102
|
+
let mut bytes = Vec::new();
|
|
1103
|
+
for &ch in buf {
|
|
1104
|
+
if ch == 0 {
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
bytes.push(ch as u8);
|
|
1108
|
+
}
|
|
1109
|
+
String::from_utf8_lossy(&bytes).into_owned()
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
fn error_buf_to_string(buf: &[c_char; J_LAW_ERROR_BUF_LEN]) -> String {
|
|
1113
|
+
let mut bytes = Vec::new();
|
|
1114
|
+
for &ch in buf {
|
|
1115
|
+
if ch == 0 {
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
bytes.push(ch as u8);
|
|
1119
|
+
}
|
|
1120
|
+
String::from_utf8_lossy(&bytes).into_owned()
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
fn sample_income_deduction_input() -> JLawIncomeDeductionInput {
|
|
1124
|
+
JLawIncomeDeductionInput {
|
|
1125
|
+
total_income_amount: 6_000_000,
|
|
1126
|
+
year: 2024,
|
|
1127
|
+
month: 1,
|
|
1128
|
+
day: 1,
|
|
1129
|
+
has_spouse: 0,
|
|
1130
|
+
spouse_total_income_amount: 0,
|
|
1131
|
+
spouse_is_same_household: 0,
|
|
1132
|
+
spouse_is_elderly: 0,
|
|
1133
|
+
dependent_general_count: 0,
|
|
1134
|
+
dependent_specific_count: 0,
|
|
1135
|
+
dependent_elderly_cohabiting_count: 0,
|
|
1136
|
+
dependent_elderly_other_count: 0,
|
|
1137
|
+
social_insurance_premium_paid: 150_000,
|
|
1138
|
+
has_medical: 1,
|
|
1139
|
+
medical_expense_paid: 500_000,
|
|
1140
|
+
medical_reimbursed_amount: 50_000,
|
|
1141
|
+
has_life_insurance: 1,
|
|
1142
|
+
life_new_general_paid_amount: 100_000,
|
|
1143
|
+
life_new_individual_pension_paid_amount: 60_000,
|
|
1144
|
+
life_new_care_medical_paid_amount: 80_000,
|
|
1145
|
+
life_old_general_paid_amount: 0,
|
|
1146
|
+
life_old_individual_pension_paid_amount: 0,
|
|
1147
|
+
has_donation: 1,
|
|
1148
|
+
donation_qualified_amount: 500_000,
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
#[test]
|
|
1153
|
+
fn ffi_version_matches_constant() {
|
|
1154
|
+
assert_eq!(j_law_c_ffi_version(), J_LAW_C_FFI_VERSION);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
#[test]
|
|
1158
|
+
fn brokerage_fee_writes_expected_c_result() {
|
|
1159
|
+
let mut result = unsafe { std::mem::zeroed::<JLawBrokerageFeeResult>() };
|
|
1160
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1161
|
+
|
|
1162
|
+
let status = unsafe {
|
|
1163
|
+
j_law_calc_brokerage_fee(
|
|
1164
|
+
5_000_000,
|
|
1165
|
+
2024,
|
|
1166
|
+
8,
|
|
1167
|
+
1,
|
|
1168
|
+
0,
|
|
1169
|
+
0,
|
|
1170
|
+
&mut result,
|
|
1171
|
+
error_buf.as_mut_ptr(),
|
|
1172
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1173
|
+
)
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
assert_eq!(status, 0);
|
|
1177
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1178
|
+
assert_eq!(result.total_without_tax, 210_000);
|
|
1179
|
+
assert_eq!(result.tax_amount, 21_000);
|
|
1180
|
+
assert_eq!(result.total_with_tax, 231_000);
|
|
1181
|
+
assert_eq!(result.low_cost_special_applied, 0);
|
|
1182
|
+
assert_eq!(result.breakdown_len, 3);
|
|
1183
|
+
assert_eq!(fixed_buf_to_string(&result.breakdown[0].label), "tier1");
|
|
1184
|
+
assert_eq!(result.breakdown[0].base_amount, 2_000_000);
|
|
1185
|
+
assert_eq!(result.breakdown[0].result, 100_000);
|
|
1186
|
+
assert_eq!(fixed_buf_to_string(&result.breakdown[2].label), "tier3");
|
|
1187
|
+
assert_eq!(result.breakdown[2].base_amount, 1_000_000);
|
|
1188
|
+
assert_eq!(result.breakdown[2].result, 30_000);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
#[test]
|
|
1192
|
+
fn brokerage_fee_propagates_errors() {
|
|
1193
|
+
let mut result = unsafe { std::mem::zeroed::<JLawBrokerageFeeResult>() };
|
|
1194
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1195
|
+
|
|
1196
|
+
let status = unsafe {
|
|
1197
|
+
j_law_calc_brokerage_fee(
|
|
1198
|
+
5_000_000,
|
|
1199
|
+
1970,
|
|
1200
|
+
11,
|
|
1201
|
+
30,
|
|
1202
|
+
0,
|
|
1203
|
+
0,
|
|
1204
|
+
&mut result,
|
|
1205
|
+
error_buf.as_mut_ptr(),
|
|
1206
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1207
|
+
)
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
assert_eq!(status, 1);
|
|
1211
|
+
assert!(error_buf_to_string(&error_buf).contains("1970-11-30"));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
#[test]
|
|
1215
|
+
fn withholding_tax_writes_expected_c_result() {
|
|
1216
|
+
let mut result = unsafe { std::mem::zeroed::<JLawWithholdingTaxResult>() };
|
|
1217
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1218
|
+
|
|
1219
|
+
let status = unsafe {
|
|
1220
|
+
j_law_calc_withholding_tax(
|
|
1221
|
+
1_500_000,
|
|
1222
|
+
0,
|
|
1223
|
+
2026,
|
|
1224
|
+
1,
|
|
1225
|
+
1,
|
|
1226
|
+
J_LAW_WITHHOLDING_TAX_CATEGORY_PROFESSIONAL_FEE,
|
|
1227
|
+
0,
|
|
1228
|
+
&mut result,
|
|
1229
|
+
error_buf.as_mut_ptr(),
|
|
1230
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1231
|
+
)
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
assert_eq!(status, 0);
|
|
1235
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1236
|
+
assert_eq!(result.gross_payment_amount, 1_500_000);
|
|
1237
|
+
assert_eq!(result.taxable_payment_amount, 1_500_000);
|
|
1238
|
+
assert_eq!(result.tax_amount, 204_200);
|
|
1239
|
+
assert_eq!(result.net_payment_amount, 1_295_800);
|
|
1240
|
+
assert_eq!(
|
|
1241
|
+
result.category,
|
|
1242
|
+
J_LAW_WITHHOLDING_TAX_CATEGORY_PROFESSIONAL_FEE
|
|
1243
|
+
);
|
|
1244
|
+
assert_eq!(result.submission_prize_exempted, 0);
|
|
1245
|
+
assert_eq!(result.breakdown_len, 2);
|
|
1246
|
+
assert_eq!(
|
|
1247
|
+
fixed_buf_to_string(&result.breakdown[0].label),
|
|
1248
|
+
"1000000円以下の部分"
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
#[test]
|
|
1253
|
+
fn withholding_tax_propagates_errors() {
|
|
1254
|
+
let mut result = unsafe { std::mem::zeroed::<JLawWithholdingTaxResult>() };
|
|
1255
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1256
|
+
|
|
1257
|
+
let status = unsafe {
|
|
1258
|
+
j_law_calc_withholding_tax(
|
|
1259
|
+
100_000,
|
|
1260
|
+
100_001,
|
|
1261
|
+
2026,
|
|
1262
|
+
1,
|
|
1263
|
+
1,
|
|
1264
|
+
J_LAW_WITHHOLDING_TAX_CATEGORY_MANUSCRIPT_AND_LECTURE,
|
|
1265
|
+
0,
|
|
1266
|
+
&mut result,
|
|
1267
|
+
error_buf.as_mut_ptr(),
|
|
1268
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1269
|
+
)
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
assert_eq!(status, 1);
|
|
1273
|
+
assert!(error_buf_to_string(&error_buf).contains("separated_consumption_tax_amount"));
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
#[test]
|
|
1277
|
+
fn income_tax_writes_expected_c_result() {
|
|
1278
|
+
let mut result = unsafe { std::mem::zeroed::<JLawIncomeTaxResult>() };
|
|
1279
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1280
|
+
|
|
1281
|
+
let status = unsafe {
|
|
1282
|
+
j_law_calc_income_tax(
|
|
1283
|
+
5_000_000,
|
|
1284
|
+
2024,
|
|
1285
|
+
1,
|
|
1286
|
+
1,
|
|
1287
|
+
1,
|
|
1288
|
+
&mut result,
|
|
1289
|
+
error_buf.as_mut_ptr(),
|
|
1290
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1291
|
+
)
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
assert_eq!(status, 0);
|
|
1295
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1296
|
+
assert_eq!(result.base_tax, 572_500);
|
|
1297
|
+
assert_eq!(result.reconstruction_tax, 12_022);
|
|
1298
|
+
assert_eq!(result.total_tax, 584_500);
|
|
1299
|
+
assert_eq!(result.reconstruction_tax_applied, 1);
|
|
1300
|
+
assert_eq!(result.breakdown_len, 1);
|
|
1301
|
+
assert_eq!(
|
|
1302
|
+
fixed_buf_to_string(&result.breakdown[0].label),
|
|
1303
|
+
"330万円超695万円以下"
|
|
1304
|
+
);
|
|
1305
|
+
assert_eq!(result.breakdown[0].taxable_income, 5_000_000);
|
|
1306
|
+
assert_eq!(result.breakdown[0].deduction, 427_500);
|
|
1307
|
+
assert_eq!(result.breakdown[0].result, 572_500);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
#[test]
|
|
1311
|
+
fn income_tax_propagates_errors() {
|
|
1312
|
+
let mut result = unsafe { std::mem::zeroed::<JLawIncomeTaxResult>() };
|
|
1313
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1314
|
+
|
|
1315
|
+
let status = unsafe {
|
|
1316
|
+
j_law_calc_income_tax(
|
|
1317
|
+
5_000_000,
|
|
1318
|
+
1988,
|
|
1319
|
+
12,
|
|
1320
|
+
31,
|
|
1321
|
+
1,
|
|
1322
|
+
&mut result,
|
|
1323
|
+
error_buf.as_mut_ptr(),
|
|
1324
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1325
|
+
)
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
assert_eq!(status, 1);
|
|
1329
|
+
assert!(error_buf_to_string(&error_buf).contains("1988-12-31"));
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
#[test]
|
|
1333
|
+
fn income_deductions_write_expected_c_result() {
|
|
1334
|
+
let input = sample_income_deduction_input();
|
|
1335
|
+
let mut result = unsafe { std::mem::zeroed::<JLawIncomeDeductionResult>() };
|
|
1336
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1337
|
+
|
|
1338
|
+
let status = unsafe {
|
|
1339
|
+
j_law_calc_income_deductions(
|
|
1340
|
+
&input,
|
|
1341
|
+
&mut result,
|
|
1342
|
+
error_buf.as_mut_ptr(),
|
|
1343
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1344
|
+
)
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
assert_eq!(status, 0);
|
|
1348
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1349
|
+
assert_eq!(result.total_income_amount, 6_000_000);
|
|
1350
|
+
assert_eq!(result.total_deductions, 1_593_000);
|
|
1351
|
+
assert_eq!(result.taxable_income_before_truncation, 4_407_000);
|
|
1352
|
+
assert_eq!(result.taxable_income, 4_407_000);
|
|
1353
|
+
assert_eq!(result.breakdown_len, 7);
|
|
1354
|
+
assert_eq!(result.breakdown[0].kind, 1);
|
|
1355
|
+
assert_eq!(fixed_buf_to_string(&result.breakdown[0].label), "基礎控除");
|
|
1356
|
+
assert_eq!(result.breakdown[4].amount, 350_000);
|
|
1357
|
+
assert_eq!(result.breakdown[5].amount, 115_000);
|
|
1358
|
+
assert_eq!(result.breakdown[6].amount, 498_000);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
#[test]
|
|
1362
|
+
fn income_tax_assessment_writes_expected_c_result() {
|
|
1363
|
+
let input = sample_income_deduction_input();
|
|
1364
|
+
let mut result = unsafe { std::mem::zeroed::<JLawIncomeTaxAssessmentResult>() };
|
|
1365
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1366
|
+
|
|
1367
|
+
let status = unsafe {
|
|
1368
|
+
j_law_calc_income_tax_assessment(
|
|
1369
|
+
&input,
|
|
1370
|
+
1,
|
|
1371
|
+
&mut result,
|
|
1372
|
+
error_buf.as_mut_ptr(),
|
|
1373
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1374
|
+
)
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
assert_eq!(status, 0);
|
|
1378
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1379
|
+
assert_eq!(result.total_deductions, 1_593_000);
|
|
1380
|
+
assert_eq!(result.taxable_income, 4_407_000);
|
|
1381
|
+
assert_eq!(result.base_tax, 453_900);
|
|
1382
|
+
assert_eq!(result.reconstruction_tax, 9_531);
|
|
1383
|
+
assert_eq!(result.total_tax, 463_400);
|
|
1384
|
+
assert_eq!(result.reconstruction_tax_applied, 1);
|
|
1385
|
+
assert_eq!(result.deduction_breakdown_len, 7);
|
|
1386
|
+
assert_eq!(result.tax_breakdown_len, 1);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
#[test]
|
|
1390
|
+
fn consumption_tax_writes_expected_c_result() {
|
|
1391
|
+
let mut result = unsafe { std::mem::zeroed::<JLawConsumptionTaxResult>() };
|
|
1392
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1393
|
+
|
|
1394
|
+
let status = unsafe {
|
|
1395
|
+
j_law_calc_consumption_tax(
|
|
1396
|
+
100_000,
|
|
1397
|
+
2024,
|
|
1398
|
+
1,
|
|
1399
|
+
1,
|
|
1400
|
+
0,
|
|
1401
|
+
&mut result,
|
|
1402
|
+
error_buf.as_mut_ptr(),
|
|
1403
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1404
|
+
)
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
assert_eq!(status, 0);
|
|
1408
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1409
|
+
assert_eq!(result.tax_amount, 10_000);
|
|
1410
|
+
assert_eq!(result.amount_with_tax, 110_000);
|
|
1411
|
+
assert_eq!(result.amount_without_tax, 100_000);
|
|
1412
|
+
assert_eq!(result.applied_rate_numer, 10);
|
|
1413
|
+
assert_eq!(result.applied_rate_denom, 100);
|
|
1414
|
+
assert_eq!(result.is_reduced_rate, 0);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
#[test]
|
|
1418
|
+
fn consumption_tax_propagates_errors() {
|
|
1419
|
+
let mut result = unsafe { std::mem::zeroed::<JLawConsumptionTaxResult>() };
|
|
1420
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1421
|
+
|
|
1422
|
+
let status = unsafe {
|
|
1423
|
+
j_law_calc_consumption_tax(
|
|
1424
|
+
100_000,
|
|
1425
|
+
2016,
|
|
1426
|
+
1,
|
|
1427
|
+
1,
|
|
1428
|
+
1,
|
|
1429
|
+
&mut result,
|
|
1430
|
+
error_buf.as_mut_ptr(),
|
|
1431
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1432
|
+
)
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
assert_eq!(status, 1);
|
|
1436
|
+
assert!(
|
|
1437
|
+
error_buf_to_string(&error_buf).contains("軽減税率"),
|
|
1438
|
+
"unexpected error: {}",
|
|
1439
|
+
error_buf_to_string(&error_buf)
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
#[test]
|
|
1444
|
+
fn stamp_tax_writes_expected_c_result() {
|
|
1445
|
+
let mut result = unsafe { std::mem::zeroed::<JLawStampTaxResult>() };
|
|
1446
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1447
|
+
|
|
1448
|
+
let status = unsafe {
|
|
1449
|
+
j_law_calc_stamp_tax(
|
|
1450
|
+
StampTaxDocumentCode::Article1OtherTransfer.ffi_code(),
|
|
1451
|
+
5_000_000,
|
|
1452
|
+
1,
|
|
1453
|
+
2024,
|
|
1454
|
+
8,
|
|
1455
|
+
1,
|
|
1456
|
+
0,
|
|
1457
|
+
&mut result,
|
|
1458
|
+
error_buf.as_mut_ptr(),
|
|
1459
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1460
|
+
)
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
assert_eq!(status, 0);
|
|
1464
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1465
|
+
assert_eq!(result.tax_amount, 2_000);
|
|
1466
|
+
assert_eq!(
|
|
1467
|
+
fixed_buf_to_string(&result.rule_label),
|
|
1468
|
+
"100万円を超え500万円以下のもの"
|
|
1469
|
+
);
|
|
1470
|
+
assert_eq!(fixed_buf_to_string(&result.applied_special_rule), "");
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
#[test]
|
|
1474
|
+
fn stamp_tax_supports_flags_and_special_rules() {
|
|
1475
|
+
let mut result = unsafe { std::mem::zeroed::<JLawStampTaxResult>() };
|
|
1476
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1477
|
+
|
|
1478
|
+
let status = unsafe {
|
|
1479
|
+
j_law_calc_stamp_tax(
|
|
1480
|
+
StampTaxDocumentCode::Article17SalesReceipt.ffi_code(),
|
|
1481
|
+
70_000,
|
|
1482
|
+
1,
|
|
1483
|
+
2024,
|
|
1484
|
+
8,
|
|
1485
|
+
1,
|
|
1486
|
+
StampTaxFlag::Article17NonBusinessExempt.bitmask(),
|
|
1487
|
+
&mut result,
|
|
1488
|
+
error_buf.as_mut_ptr(),
|
|
1489
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1490
|
+
)
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
assert_eq!(status, 0);
|
|
1494
|
+
assert_eq!(error_buf_to_string(&error_buf), "");
|
|
1495
|
+
assert_eq!(result.tax_amount, 0);
|
|
1496
|
+
assert_eq!(
|
|
1497
|
+
fixed_buf_to_string(&result.rule_label),
|
|
1498
|
+
"営業に関しないもの"
|
|
1499
|
+
);
|
|
1500
|
+
assert_eq!(
|
|
1501
|
+
fixed_buf_to_string(&result.applied_special_rule),
|
|
1502
|
+
"article17_non_business_exempt"
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
#[test]
|
|
1507
|
+
fn stamp_tax_rejects_unknown_document_code() {
|
|
1508
|
+
let mut result = unsafe { std::mem::zeroed::<JLawStampTaxResult>() };
|
|
1509
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1510
|
+
|
|
1511
|
+
let status = unsafe {
|
|
1512
|
+
j_law_calc_stamp_tax(
|
|
1513
|
+
99,
|
|
1514
|
+
1_500_000,
|
|
1515
|
+
1,
|
|
1516
|
+
2024,
|
|
1517
|
+
8,
|
|
1518
|
+
1,
|
|
1519
|
+
0,
|
|
1520
|
+
&mut result,
|
|
1521
|
+
error_buf.as_mut_ptr(),
|
|
1522
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1523
|
+
)
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
assert_eq!(status, 1);
|
|
1527
|
+
assert!(error_buf_to_string(&error_buf).contains("unsupported stamp tax document code"));
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
#[test]
|
|
1531
|
+
fn stamp_tax_propagates_errors() {
|
|
1532
|
+
let mut result = unsafe { std::mem::zeroed::<JLawStampTaxResult>() };
|
|
1533
|
+
let mut error_buf = [0; J_LAW_ERROR_BUF_LEN];
|
|
1534
|
+
|
|
1535
|
+
let status = unsafe {
|
|
1536
|
+
j_law_calc_stamp_tax(
|
|
1537
|
+
StampTaxDocumentCode::Article1RealEstateTransfer.ffi_code(),
|
|
1538
|
+
5_000_000,
|
|
1539
|
+
1,
|
|
1540
|
+
2014,
|
|
1541
|
+
3,
|
|
1542
|
+
31,
|
|
1543
|
+
0,
|
|
1544
|
+
&mut result,
|
|
1545
|
+
error_buf.as_mut_ptr(),
|
|
1546
|
+
J_LAW_ERROR_BUF_LEN as c_int,
|
|
1547
|
+
)
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
assert_eq!(status, 1);
|
|
1551
|
+
assert!(error_buf_to_string(&error_buf).contains("2014-03-31"));
|
|
1552
|
+
}
|
|
1553
|
+
}
|