Skip to content

Commit 9a85553

Browse files
committed
FINERACT-2471: Implement 'Force Debit' functionality
This PR implements Phase 1 of the Force Withdrawal feature for Savings Accounts, including API endpoint, global configuration flags, command handler, boolean values extension, and integration tests. It also adds the missing FORCE_WITHDRAWAL CHECKER permission and fixes a potential NPE in SavingsAccount validation.
1 parent 02fb166 commit 9a85553

File tree

88 files changed

+11109
-26
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+11109
-26
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package org.apache.fineract.accounting.journalentry;
2+
3+
import javax.annotation.processing.Generated;
4+
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
5+
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
6+
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
7+
import org.apache.fineract.organisation.office.domain.Office;
8+
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
9+
import org.apache.fineract.portfolio.paymenttype.domain.PaymentType;
10+
import org.springframework.stereotype.Component;
11+
12+
@Generated(
13+
value = "org.mapstruct.ap.MappingProcessor",
14+
date = "2026-02-10T12:11:44+0530",
15+
comments = "version: 1.6.3, compiler: Eclipse JDT (IDE) 3.45.0.v20260128-0750, environment: Java 21.0.9 (Eclipse Adoptium)"
16+
)
17+
@Component
18+
public class JournalEntryMapperImpl implements JournalEntryMapper {
19+
20+
@Override
21+
public JournalEntryData map(JournalEntry journalEntry) {
22+
if ( journalEntry == null ) {
23+
return null;
24+
}
25+
26+
JournalEntryData journalEntryData = new JournalEntryData();
27+
28+
journalEntryData.setId( journalEntry.getId() );
29+
journalEntryData.setOfficeId( journalEntryOfficeId( journalEntry ) );
30+
journalEntryData.setOfficeName( journalEntryOfficeName( journalEntry ) );
31+
journalEntryData.setGlAccountId( journalEntryGlAccountId( journalEntry ) );
32+
journalEntryData.setGlAccountCode( journalEntryGlAccountGlCode( journalEntry ) );
33+
journalEntryData.setGlAccountName( journalEntryGlAccountName( journalEntry ) );
34+
journalEntryData.setGlAccountType( mapGlAccountType( mapGlAccountType( journalEntryGlAccountType( journalEntry ) ) ) );
35+
journalEntryData.setTransactionDate( journalEntry.getTransactionDate() );
36+
journalEntryData.setEntryType( mapJournalEntryType( mapJournalEntryType( journalEntry.getType() ) ) );
37+
journalEntryData.setAmount( journalEntry.getAmount() );
38+
journalEntryData.setEntityType( mapEntityType( mapEntityType( journalEntry.getEntityType() ) ) );
39+
journalEntryData.setEntityId( journalEntry.getEntityId() );
40+
journalEntryData.setSubmittedOnDate( journalEntry.getSubmittedOnDate() );
41+
journalEntryData.setTransactionId( journalEntry.getTransactionId() );
42+
journalEntryData.setCurrency( mapCurrency( journalEntry.getCurrencyCode() ) );
43+
journalEntryData.setManualEntry( journalEntry.isManualEntry() );
44+
journalEntryData.setReversed( journalEntry.isReversed() );
45+
journalEntryData.setReferenceNumber( journalEntry.getReferenceNumber() );
46+
journalEntryData.setPaymentTypeId( journalEntryPaymentDetailPaymentTypeId( journalEntry ) );
47+
journalEntryData.setAccountNumber( journalEntryPaymentDetailAccountNumber( journalEntry ) );
48+
journalEntryData.setCheckNumber( journalEntryPaymentDetailCheckNumber( journalEntry ) );
49+
journalEntryData.setRoutingCode( journalEntryPaymentDetailRoutingCode( journalEntry ) );
50+
journalEntryData.setReceiptNumber( journalEntryPaymentDetailReceiptNumber( journalEntry ) );
51+
journalEntryData.setBankNumber( journalEntryPaymentDetailBankNumber( journalEntry ) );
52+
journalEntryData.setCurrencyCode( journalEntry.getCurrencyCode() );
53+
54+
return journalEntryData;
55+
}
56+
57+
private Long journalEntryOfficeId(JournalEntry journalEntry) {
58+
Office office = journalEntry.getOffice();
59+
if ( office == null ) {
60+
return null;
61+
}
62+
return office.getId();
63+
}
64+
65+
private String journalEntryOfficeName(JournalEntry journalEntry) {
66+
Office office = journalEntry.getOffice();
67+
if ( office == null ) {
68+
return null;
69+
}
70+
return office.getName();
71+
}
72+
73+
private Long journalEntryGlAccountId(JournalEntry journalEntry) {
74+
GLAccount glAccount = journalEntry.getGlAccount();
75+
if ( glAccount == null ) {
76+
return null;
77+
}
78+
return glAccount.getId();
79+
}
80+
81+
private String journalEntryGlAccountGlCode(JournalEntry journalEntry) {
82+
GLAccount glAccount = journalEntry.getGlAccount();
83+
if ( glAccount == null ) {
84+
return null;
85+
}
86+
return glAccount.getGlCode();
87+
}
88+
89+
private String journalEntryGlAccountName(JournalEntry journalEntry) {
90+
GLAccount glAccount = journalEntry.getGlAccount();
91+
if ( glAccount == null ) {
92+
return null;
93+
}
94+
return glAccount.getName();
95+
}
96+
97+
private Integer journalEntryGlAccountType(JournalEntry journalEntry) {
98+
GLAccount glAccount = journalEntry.getGlAccount();
99+
if ( glAccount == null ) {
100+
return null;
101+
}
102+
return glAccount.getType();
103+
}
104+
105+
private Long journalEntryPaymentDetailPaymentTypeId(JournalEntry journalEntry) {
106+
PaymentDetail paymentDetail = journalEntry.getPaymentDetail();
107+
if ( paymentDetail == null ) {
108+
return null;
109+
}
110+
PaymentType paymentType = paymentDetail.getPaymentType();
111+
if ( paymentType == null ) {
112+
return null;
113+
}
114+
return paymentType.getId();
115+
}
116+
117+
private String journalEntryPaymentDetailAccountNumber(JournalEntry journalEntry) {
118+
PaymentDetail paymentDetail = journalEntry.getPaymentDetail();
119+
if ( paymentDetail == null ) {
120+
return null;
121+
}
122+
return paymentDetail.getAccountNumber();
123+
}
124+
125+
private String journalEntryPaymentDetailCheckNumber(JournalEntry journalEntry) {
126+
PaymentDetail paymentDetail = journalEntry.getPaymentDetail();
127+
if ( paymentDetail == null ) {
128+
return null;
129+
}
130+
return paymentDetail.getCheckNumber();
131+
}
132+
133+
private String journalEntryPaymentDetailRoutingCode(JournalEntry journalEntry) {
134+
PaymentDetail paymentDetail = journalEntry.getPaymentDetail();
135+
if ( paymentDetail == null ) {
136+
return null;
137+
}
138+
return paymentDetail.getRoutingCode();
139+
}
140+
141+
private String journalEntryPaymentDetailReceiptNumber(JournalEntry journalEntry) {
142+
PaymentDetail paymentDetail = journalEntry.getPaymentDetail();
143+
if ( paymentDetail == null ) {
144+
return null;
145+
}
146+
return paymentDetail.getReceiptNumber();
147+
}
148+
149+
private String journalEntryPaymentDetailBankNumber(JournalEntry journalEntry) {
150+
PaymentDetail paymentDetail = journalEntry.getPaymentDetail();
151+
if ( paymentDetail == null ) {
152+
return null;
153+
}
154+
return paymentDetail.getBankNumber();
155+
}
156+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.apache.fineract.cob.service;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import javax.annotation.processing.Generated;
6+
import org.apache.fineract.cob.data.BusinessStep;
7+
import org.apache.fineract.cob.domain.BatchBusinessStep;
8+
import org.springframework.stereotype.Component;
9+
10+
@Generated(
11+
value = "org.mapstruct.ap.MappingProcessor",
12+
date = "2026-02-10T12:11:41+0530",
13+
comments = "version: 1.6.3, compiler: Eclipse JDT (IDE) 3.45.0.v20260128-0750, environment: Java 21.0.9 (Eclipse Adoptium)"
14+
)
15+
@Component
16+
public class BusinessStepMapperImpl implements BusinessStepMapper {
17+
18+
@Override
19+
public BusinessStep map(BatchBusinessStep source) {
20+
if ( source == null ) {
21+
return null;
22+
}
23+
24+
BusinessStep businessStep = new BusinessStep();
25+
26+
businessStep.setOrder( source.getStepOrder() );
27+
businessStep.setStepName( source.getStepName() );
28+
29+
return businessStep;
30+
}
31+
32+
@Override
33+
public List<BusinessStep> map(List<BatchBusinessStep> source) {
34+
if ( source == null ) {
35+
return null;
36+
}
37+
38+
List<BusinessStep> list = new ArrayList<BusinessStep>( source.size() );
39+
for ( BatchBusinessStep batchBusinessStep : source ) {
40+
list.add( map( batchBusinessStep ) );
41+
}
42+
43+
return list;
44+
}
45+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.apache.fineract.command.persistence.mapping;
2+
3+
import javax.annotation.processing.Generated;
4+
import org.apache.fineract.command.core.Command;
5+
import org.apache.fineract.command.persistence.domain.CommandEntity;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.stereotype.Component;
8+
9+
@Generated(
10+
value = "org.mapstruct.ap.MappingProcessor",
11+
date = "2026-02-10T12:11:35+0530",
12+
comments = "version: 1.6.3, compiler: Eclipse JDT (IDE) 3.45.0.v20260128-0750, environment: Java 21.0.9 (Eclipse Adoptium)"
13+
)
14+
@Component
15+
public class CommandMapperImpl implements CommandMapper {
16+
17+
private final CommandJsonMapper commandJsonMapper;
18+
19+
@Autowired
20+
public CommandMapperImpl(CommandJsonMapper commandJsonMapper) {
21+
22+
this.commandJsonMapper = commandJsonMapper;
23+
}
24+
25+
@Override
26+
public CommandEntity map(Command source) {
27+
if ( source == null ) {
28+
return null;
29+
}
30+
31+
CommandEntity commandEntity = new CommandEntity();
32+
33+
commandEntity.setId( source.getCommandId() );
34+
commandEntity.setCreatedAt( source.getCreatedAt() );
35+
commandEntity.setUpdatedAt( source.getUpdatedAt() );
36+
commandEntity.setExecutedAt( source.getExecutedAt() );
37+
commandEntity.setApprovedAt( source.getApprovedAt() );
38+
commandEntity.setRejectedAt( source.getRejectedAt() );
39+
commandEntity.setInitiatedByUsername( source.getInitiatedByUsername() );
40+
commandEntity.setExecutedByUsername( source.getExecutedByUsername() );
41+
commandEntity.setApprovedByUsername( source.getApprovedByUsername() );
42+
commandEntity.setRejectedByUsername( source.getRejectedByUsername() );
43+
commandEntity.setRequest( commandJsonMapper.map( source.getPayload() ) );
44+
commandEntity.setIpAddress( source.getIpAddress() );
45+
commandEntity.setIdempotencyKey( source.getIdempotencyKey() );
46+
commandEntity.setError( source.getError() );
47+
48+
return commandEntity;
49+
}
50+
51+
@Override
52+
public CommandEntity map(Command source, Object response) {
53+
if ( source == null && response == null ) {
54+
return null;
55+
}
56+
57+
CommandEntity commandEntity = new CommandEntity();
58+
59+
if ( source != null ) {
60+
commandEntity.setId( source.getCommandId() );
61+
commandEntity.setCreatedAt( source.getCreatedAt() );
62+
commandEntity.setUpdatedAt( source.getUpdatedAt() );
63+
commandEntity.setExecutedAt( source.getExecutedAt() );
64+
commandEntity.setApprovedAt( source.getApprovedAt() );
65+
commandEntity.setRejectedAt( source.getRejectedAt() );
66+
commandEntity.setInitiatedByUsername( source.getInitiatedByUsername() );
67+
commandEntity.setExecutedByUsername( source.getExecutedByUsername() );
68+
commandEntity.setApprovedByUsername( source.getApprovedByUsername() );
69+
commandEntity.setRejectedByUsername( source.getRejectedByUsername() );
70+
commandEntity.setRequest( commandJsonMapper.map( source.getPayload() ) );
71+
commandEntity.setIpAddress( source.getIpAddress() );
72+
commandEntity.setIdempotencyKey( source.getIdempotencyKey() );
73+
commandEntity.setError( source.getError() );
74+
}
75+
commandEntity.setResponse( commandJsonMapper.map( response ) );
76+
77+
return commandEntity;
78+
}
79+
80+
@Override
81+
public Command map(CommandEntity source) {
82+
if ( source == null ) {
83+
return null;
84+
}
85+
86+
Command command = new Command();
87+
88+
command.setCommandId( source.getId() );
89+
command.setCreatedAt( source.getCreatedAt() );
90+
command.setUpdatedAt( source.getUpdatedAt() );
91+
command.setExecutedAt( source.getExecutedAt() );
92+
command.setApprovedAt( source.getApprovedAt() );
93+
command.setRejectedAt( source.getRejectedAt() );
94+
command.setInitiatedByUsername( source.getInitiatedByUsername() );
95+
command.setExecutedByUsername( source.getExecutedByUsername() );
96+
command.setApprovedByUsername( source.getApprovedByUsername() );
97+
command.setRejectedByUsername( source.getRejectedByUsername() );
98+
command.setPayload( commandJsonMapper.map( source.getRequest() ) );
99+
command.setIpAddress( source.getIpAddress() );
100+
command.setIdempotencyKey( source.getIdempotencyKey() );
101+
command.setError( source.getError() );
102+
103+
return command;
104+
}
105+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.apache.fineract.command.jmh_generated;
2+
public class CommandPipelineBenchmark_jmhType extends CommandPipelineBenchmark_jmhType_B3 {
3+
}
4+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.apache.fineract.command.jmh_generated;
2+
import org.apache.fineract.command.CommandPipelineBenchmark;
3+
public class CommandPipelineBenchmark_jmhType_B1 extends org.apache.fineract.command.CommandPipelineBenchmark {
4+
byte b1_000, b1_001, b1_002, b1_003, b1_004, b1_005, b1_006, b1_007, b1_008, b1_009, b1_010, b1_011, b1_012, b1_013, b1_014, b1_015;
5+
byte b1_016, b1_017, b1_018, b1_019, b1_020, b1_021, b1_022, b1_023, b1_024, b1_025, b1_026, b1_027, b1_028, b1_029, b1_030, b1_031;
6+
byte b1_032, b1_033, b1_034, b1_035, b1_036, b1_037, b1_038, b1_039, b1_040, b1_041, b1_042, b1_043, b1_044, b1_045, b1_046, b1_047;
7+
byte b1_048, b1_049, b1_050, b1_051, b1_052, b1_053, b1_054, b1_055, b1_056, b1_057, b1_058, b1_059, b1_060, b1_061, b1_062, b1_063;
8+
byte b1_064, b1_065, b1_066, b1_067, b1_068, b1_069, b1_070, b1_071, b1_072, b1_073, b1_074, b1_075, b1_076, b1_077, b1_078, b1_079;
9+
byte b1_080, b1_081, b1_082, b1_083, b1_084, b1_085, b1_086, b1_087, b1_088, b1_089, b1_090, b1_091, b1_092, b1_093, b1_094, b1_095;
10+
byte b1_096, b1_097, b1_098, b1_099, b1_100, b1_101, b1_102, b1_103, b1_104, b1_105, b1_106, b1_107, b1_108, b1_109, b1_110, b1_111;
11+
byte b1_112, b1_113, b1_114, b1_115, b1_116, b1_117, b1_118, b1_119, b1_120, b1_121, b1_122, b1_123, b1_124, b1_125, b1_126, b1_127;
12+
byte b1_128, b1_129, b1_130, b1_131, b1_132, b1_133, b1_134, b1_135, b1_136, b1_137, b1_138, b1_139, b1_140, b1_141, b1_142, b1_143;
13+
byte b1_144, b1_145, b1_146, b1_147, b1_148, b1_149, b1_150, b1_151, b1_152, b1_153, b1_154, b1_155, b1_156, b1_157, b1_158, b1_159;
14+
byte b1_160, b1_161, b1_162, b1_163, b1_164, b1_165, b1_166, b1_167, b1_168, b1_169, b1_170, b1_171, b1_172, b1_173, b1_174, b1_175;
15+
byte b1_176, b1_177, b1_178, b1_179, b1_180, b1_181, b1_182, b1_183, b1_184, b1_185, b1_186, b1_187, b1_188, b1_189, b1_190, b1_191;
16+
byte b1_192, b1_193, b1_194, b1_195, b1_196, b1_197, b1_198, b1_199, b1_200, b1_201, b1_202, b1_203, b1_204, b1_205, b1_206, b1_207;
17+
byte b1_208, b1_209, b1_210, b1_211, b1_212, b1_213, b1_214, b1_215, b1_216, b1_217, b1_218, b1_219, b1_220, b1_221, b1_222, b1_223;
18+
byte b1_224, b1_225, b1_226, b1_227, b1_228, b1_229, b1_230, b1_231, b1_232, b1_233, b1_234, b1_235, b1_236, b1_237, b1_238, b1_239;
19+
byte b1_240, b1_241, b1_242, b1_243, b1_244, b1_245, b1_246, b1_247, b1_248, b1_249, b1_250, b1_251, b1_252, b1_253, b1_254, b1_255;
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.apache.fineract.command.jmh_generated;
2+
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
3+
public class CommandPipelineBenchmark_jmhType_B2 extends CommandPipelineBenchmark_jmhType_B1 {
4+
public volatile int setupTrialMutex;
5+
public volatile int tearTrialMutex;
6+
public final static AtomicIntegerFieldUpdater<CommandPipelineBenchmark_jmhType_B2> setupTrialMutexUpdater = AtomicIntegerFieldUpdater.newUpdater(CommandPipelineBenchmark_jmhType_B2.class, "setupTrialMutex");
7+
public final static AtomicIntegerFieldUpdater<CommandPipelineBenchmark_jmhType_B2> tearTrialMutexUpdater = AtomicIntegerFieldUpdater.newUpdater(CommandPipelineBenchmark_jmhType_B2.class, "tearTrialMutex");
8+
9+
public volatile int setupIterationMutex;
10+
public volatile int tearIterationMutex;
11+
public final static AtomicIntegerFieldUpdater<CommandPipelineBenchmark_jmhType_B2> setupIterationMutexUpdater = AtomicIntegerFieldUpdater.newUpdater(CommandPipelineBenchmark_jmhType_B2.class, "setupIterationMutex");
12+
public final static AtomicIntegerFieldUpdater<CommandPipelineBenchmark_jmhType_B2> tearIterationMutexUpdater = AtomicIntegerFieldUpdater.newUpdater(CommandPipelineBenchmark_jmhType_B2.class, "tearIterationMutex");
13+
14+
public volatile int setupInvocationMutex;
15+
public volatile int tearInvocationMutex;
16+
public final static AtomicIntegerFieldUpdater<CommandPipelineBenchmark_jmhType_B2> setupInvocationMutexUpdater = AtomicIntegerFieldUpdater.newUpdater(CommandPipelineBenchmark_jmhType_B2.class, "setupInvocationMutex");
17+
public final static AtomicIntegerFieldUpdater<CommandPipelineBenchmark_jmhType_B2> tearInvocationMutexUpdater = AtomicIntegerFieldUpdater.newUpdater(CommandPipelineBenchmark_jmhType_B2.class, "tearInvocationMutex");
18+
19+
public volatile boolean readyTrial;
20+
public volatile boolean readyIteration;
21+
public volatile boolean readyInvocation;
22+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.apache.fineract.command.jmh_generated;
2+
public class CommandPipelineBenchmark_jmhType_B3 extends CommandPipelineBenchmark_jmhType_B2 {
3+
byte b3_000, b3_001, b3_002, b3_003, b3_004, b3_005, b3_006, b3_007, b3_008, b3_009, b3_010, b3_011, b3_012, b3_013, b3_014, b3_015;
4+
byte b3_016, b3_017, b3_018, b3_019, b3_020, b3_021, b3_022, b3_023, b3_024, b3_025, b3_026, b3_027, b3_028, b3_029, b3_030, b3_031;
5+
byte b3_032, b3_033, b3_034, b3_035, b3_036, b3_037, b3_038, b3_039, b3_040, b3_041, b3_042, b3_043, b3_044, b3_045, b3_046, b3_047;
6+
byte b3_048, b3_049, b3_050, b3_051, b3_052, b3_053, b3_054, b3_055, b3_056, b3_057, b3_058, b3_059, b3_060, b3_061, b3_062, b3_063;
7+
byte b3_064, b3_065, b3_066, b3_067, b3_068, b3_069, b3_070, b3_071, b3_072, b3_073, b3_074, b3_075, b3_076, b3_077, b3_078, b3_079;
8+
byte b3_080, b3_081, b3_082, b3_083, b3_084, b3_085, b3_086, b3_087, b3_088, b3_089, b3_090, b3_091, b3_092, b3_093, b3_094, b3_095;
9+
byte b3_096, b3_097, b3_098, b3_099, b3_100, b3_101, b3_102, b3_103, b3_104, b3_105, b3_106, b3_107, b3_108, b3_109, b3_110, b3_111;
10+
byte b3_112, b3_113, b3_114, b3_115, b3_116, b3_117, b3_118, b3_119, b3_120, b3_121, b3_122, b3_123, b3_124, b3_125, b3_126, b3_127;
11+
byte b3_128, b3_129, b3_130, b3_131, b3_132, b3_133, b3_134, b3_135, b3_136, b3_137, b3_138, b3_139, b3_140, b3_141, b3_142, b3_143;
12+
byte b3_144, b3_145, b3_146, b3_147, b3_148, b3_149, b3_150, b3_151, b3_152, b3_153, b3_154, b3_155, b3_156, b3_157, b3_158, b3_159;
13+
byte b3_160, b3_161, b3_162, b3_163, b3_164, b3_165, b3_166, b3_167, b3_168, b3_169, b3_170, b3_171, b3_172, b3_173, b3_174, b3_175;
14+
byte b3_176, b3_177, b3_178, b3_179, b3_180, b3_181, b3_182, b3_183, b3_184, b3_185, b3_186, b3_187, b3_188, b3_189, b3_190, b3_191;
15+
byte b3_192, b3_193, b3_194, b3_195, b3_196, b3_197, b3_198, b3_199, b3_200, b3_201, b3_202, b3_203, b3_204, b3_205, b3_206, b3_207;
16+
byte b3_208, b3_209, b3_210, b3_211, b3_212, b3_213, b3_214, b3_215, b3_216, b3_217, b3_218, b3_219, b3_220, b3_221, b3_222, b3_223;
17+
byte b3_224, b3_225, b3_226, b3_227, b3_228, b3_229, b3_230, b3_231, b3_232, b3_233, b3_234, b3_235, b3_236, b3_237, b3_238, b3_239;
18+
byte b3_240, b3_241, b3_242, b3_243, b3_244, b3_245, b3_246, b3_247, b3_248, b3_249, b3_250, b3_251, b3_252, b3_253, b3_254, b3_255;
19+
}
20+

0 commit comments

Comments
 (0)