feat(dds): stronger DDS records, gapless account numbers, locked terms, slab validation
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Waiting to run
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Waiting to run
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Waiting to run
Fineract Documentation build / build (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Waiting to run
Fineract Liquibase Only mode - PostgreSQL / test (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Waiting to run
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Waiting to run
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Waiting to run
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Waiting to run
Fineract Documentation build / build (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Waiting to run
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Waiting to run
Fineract Liquibase Only mode - PostgreSQL / test (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Waiting to run
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Waiting to run
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Waiting to run
- Optimistic locks (version) on party, agent, batch run and collection entry
- Passbook lines keep branch, entry date, unique ref_no and the journal id they were
booked under; journal legs carry the voucher; withdrawal legs of savings-backed
parties are linked; journal ids no longer built from date and amount
- Audit user/date required with FKs on every DDS table; party tied to its product by
id, with currency, activation and closure user; CHECK and FK constraints, indexes
- DDS account numbers from a locked serial (m_dds_account_number_serial) so rolled-back
saves leave no gap; existing accounts renumbered from 1 (0624)
- Active DDS accounts: scheme terms and agent cannot be changed; closed accounts cannot
be edited (validated in the API)
- Apply DDS receipt split by branch and product customer-deposits account
- Amounts limited to paise; batch runs record posted_amount and skipped rows; commission
shared only over collections counted before the paying run
- Commission slabs: chart must start at 0, have no gap or overlap, percent 0-100, paise;
correcting a slab moves its neighbour; 10 default Account slabs for a new bank (0625)
- Generating a batch preview replaces the previous un-posted preview of the same job;
DELETE /dds/batchruns/{id} discards a preview
- Partytransactions API also returns refNo, branch, entry date, journal id, created by
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.persistence.Version;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
@@ -61,6 +62,15 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
@Table(name = "m_dds_agent", uniqueConstraints = { @UniqueConstraint(columnNames = { "agent_code" }, name = "agent_code_UNIQUE") })
|
||||
public class Agent extends AbstractAuditableCustom {
|
||||
|
||||
/**
|
||||
* Optimistic lock, as a savings account has: two saves of the same row from different requests -
|
||||
* two collections at once, or a collection during a posting run - cannot both win; the second is
|
||||
* refused and retried instead of quietly overwriting the first one's balance.
|
||||
*/
|
||||
@Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "client_id", nullable = false)
|
||||
private Client client;
|
||||
|
||||
+10
@@ -69,6 +69,16 @@ public class CommissionSlab extends AbstractAuditableCustom {
|
||||
* Corrects this slab, reporting only what actually changed so the audit trail records the
|
||||
* correction rather than a wholesale rewrite.
|
||||
*/
|
||||
/** Moves where this slab starts - used when the slab before it is corrected, so the two still meet. */
|
||||
public void startAt(final BigDecimal from) {
|
||||
this.slabFrom = from;
|
||||
}
|
||||
|
||||
/** Moves where this slab ends - used when the slab after it is corrected, so the two still meet. */
|
||||
public void endAt(final BigDecimal to) {
|
||||
this.slabTo = to;
|
||||
}
|
||||
|
||||
public Map<String, Object> update(final JsonCommand command) {
|
||||
final Map<String, Object> actualChanges = new LinkedHashMap<>(4);
|
||||
|
||||
|
||||
+23
@@ -25,6 +25,7 @@ import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
@@ -45,6 +46,15 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
|
||||
@Table(name = "m_dds_batch_run")
|
||||
public class BatchRun extends AbstractAuditableCustom {
|
||||
|
||||
/**
|
||||
* Optimistic lock, as a savings account has: two saves of the same row from different requests -
|
||||
* two collections at once, or a collection during a posting run - cannot both win; the second is
|
||||
* refused and retried instead of quietly overwriting the first one's balance.
|
||||
*/
|
||||
@Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version;
|
||||
|
||||
@Column(name = "job_type", length = 40, nullable = false)
|
||||
private String jobType;
|
||||
|
||||
@@ -87,6 +97,14 @@ public class BatchRun extends AbstractAuditableCustom {
|
||||
@Column(name = "rows_posted", nullable = false)
|
||||
private Integer rowsPosted = 0;
|
||||
|
||||
/**
|
||||
* What the run actually moved, beside totalAmount - what it was generated to move. They differ when
|
||||
* rows were passed over at posting (the money had already been paid by another run), and the run
|
||||
* says so rather than reading as though the whole total went through.
|
||||
*/
|
||||
@Column(name = "posted_amount", scale = 6, precision = 19)
|
||||
private BigDecimal postedAmount;
|
||||
|
||||
/** Why a run stopped, when it did. A background run has nowhere else to say so. */
|
||||
@Column(name = "failure_reason", length = 500)
|
||||
private String failureReason;
|
||||
@@ -155,6 +173,11 @@ public class BatchRun extends AbstractAuditableCustom {
|
||||
this.rowsPosted = posted;
|
||||
}
|
||||
|
||||
public void recordPosted(final int posted, final BigDecimal amount) {
|
||||
this.rowsPosted = posted;
|
||||
this.postedAmount = amount;
|
||||
}
|
||||
|
||||
public void markPosted(final LocalDate postedOnDate) {
|
||||
this.status = STATUS_POSTED;
|
||||
this.postedOnDate = postedOnDate;
|
||||
|
||||
+10
@@ -23,6 +23,7 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
@@ -49,6 +50,15 @@ import org.apache.fineract.portfolio.dds.party.domain.Party;
|
||||
@Table(name = "m_dds_collection_entry")
|
||||
public class CollectionEntry extends AbstractAuditableCustom {
|
||||
|
||||
/**
|
||||
* Optimistic lock, as a savings account has: two saves of the same row from different requests -
|
||||
* two collections at once, or a collection during a posting run - cannot both win; the second is
|
||||
* refused and retried instead of quietly overwriting the first one's balance.
|
||||
*/
|
||||
@Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version;
|
||||
|
||||
/** Waiting for a clerk to count the cash behind it. */
|
||||
public static final String PENDING = "PENDING";
|
||||
|
||||
|
||||
+6
@@ -178,5 +178,11 @@ public final class PartyData implements Serializable {
|
||||
private final String nomineeSplitMode;
|
||||
private final String status;
|
||||
private final LocalDate closeDate;
|
||||
/** The product by id, the account currency, and who opened (made operative) and closed it - as a savings account shows. */
|
||||
private final Long accountTypeId;
|
||||
private final String currencyCode;
|
||||
private final LocalDate activatedOnDate;
|
||||
private final String activatedByUsername;
|
||||
private final String closedByUsername;
|
||||
private final Collection<PartyNomineeData> nominees;
|
||||
}
|
||||
|
||||
+54
-5
@@ -28,6 +28,7 @@ import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.persistence.Version;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
@@ -60,6 +61,15 @@ import org.apache.fineract.portfolio.client.domain.Client;
|
||||
@Table(name = "m_dds_party", uniqueConstraints = { @UniqueConstraint(columnNames = { "party_code" }, name = "party_code_UNIQUE") })
|
||||
public class Party extends AbstractAuditableCustom {
|
||||
|
||||
/**
|
||||
* Optimistic lock, as a savings account has: two saves of the same row from different requests -
|
||||
* two collections at once, or a collection during a posting run - cannot both win; the second is
|
||||
* refused and retried instead of quietly overwriting the first one's balance.
|
||||
*/
|
||||
@Version
|
||||
@Column(name = "version", nullable = false)
|
||||
private Long version;
|
||||
|
||||
/** DDS accounts carry a flat interest rate unless the officer explicitly overrides it at creation. */
|
||||
private static final BigDecimal DEFAULT_INTEREST_RATE = new BigDecimal("2");
|
||||
|
||||
@@ -304,6 +314,29 @@ public class Party extends AbstractAuditableCustom {
|
||||
@Column(name = "close_date")
|
||||
private LocalDate closeDate;
|
||||
|
||||
/**
|
||||
* The DDS product the account is on, by id - subType carries its code for the screens, but a code
|
||||
* can be renamed and an id cannot, so this is what ties the account to its product's terms and
|
||||
* ledger accounts (as m_savings_account.product_id does).
|
||||
*/
|
||||
@Column(name = "account_type_id")
|
||||
private Long accountTypeId;
|
||||
|
||||
/** The account's currency, as a savings account records its own - set from the bank's currency on opening. */
|
||||
@Column(name = "currency_code", length = 3)
|
||||
private String currencyCode;
|
||||
|
||||
/** When the account became operative, and who made it so - the checker, where the opening was approved. */
|
||||
@Column(name = "activatedon_date")
|
||||
private LocalDate activatedOnDate;
|
||||
|
||||
@Column(name = "activatedon_userid")
|
||||
private Long activatedByUserId;
|
||||
|
||||
/** Who closed the account, beside closeDate - when it was closed. */
|
||||
@Column(name = "closedon_userid")
|
||||
private Long closedByUserId;
|
||||
|
||||
@OneToMany(mappedBy = "party", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@OrderBy("id")
|
||||
private List<PartyNominee> nominees = new ArrayList<>();
|
||||
@@ -492,10 +525,13 @@ public class Party extends AbstractAuditableCustom {
|
||||
this.savingsAccountId = savingsAccountId;
|
||||
}
|
||||
|
||||
/** Numbers the account from its id, the same way 0514 numbered the accounts already open. */
|
||||
public void assignAccountNo() {
|
||||
if (this.accountNo == null && getId() != null) {
|
||||
this.accountNo = "DDS" + String.format("%09d", getId());
|
||||
/**
|
||||
* Numbers the account from the DDS account serial (m_dds_account_number_serial), not from its id: an
|
||||
* id is spent by every save that rolls back, and the bank's account numbers must run without gaps.
|
||||
*/
|
||||
public void assignAccountNo(final long serial) {
|
||||
if (this.accountNo == null) {
|
||||
this.accountNo = "DDS" + String.format("%09d", serial);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,9 +575,22 @@ public class Party extends AbstractAuditableCustom {
|
||||
* is settled to zero (a full Withdraw Deposite/payout) before it is closed, not closed with money
|
||||
* still owed to it.
|
||||
*/
|
||||
public void close(final LocalDate closeDate) {
|
||||
public void close(final LocalDate closeDate, final Long closedBy) {
|
||||
this.status = STATUS_CLOSED;
|
||||
this.closeDate = closeDate;
|
||||
this.closedByUserId = closedBy;
|
||||
}
|
||||
|
||||
/** Ties the account to its product by id; called whenever subType is set or changed. */
|
||||
public void linkProduct(final Long productId) {
|
||||
this.accountTypeId = productId;
|
||||
}
|
||||
|
||||
/** Stamps the opening: the account's currency, and the day and user that made it operative. */
|
||||
public void recordActivation(final String currency, final LocalDate on, final Long by) {
|
||||
this.currencyCode = currency;
|
||||
this.activatedOnDate = on;
|
||||
this.activatedByUserId = by;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
@@ -22,6 +22,7 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -51,4 +52,15 @@ public final class PartyTransactionData implements Serializable {
|
||||
private final BigDecimal balanceAfterTransaction;
|
||||
/** The DDS voucher this line moved under, e.g. DDS/29092026/0001; empty on lines from before vouchers. */
|
||||
private final String voucherNo;
|
||||
/** The branch the line is booked at, the business day it was recorded, and the journal it was booked under. */
|
||||
private final Long officeId;
|
||||
private final String officeName;
|
||||
private final LocalDate submittedOnDate;
|
||||
private final String journalTransactionId;
|
||||
/** The line's own unique reference (m_dds_party_transaction.ref_no). */
|
||||
private final String refNo;
|
||||
/** Who keyed the line and when, as a savings transaction shows it. */
|
||||
private final String createdByUsername;
|
||||
private final LocalDateTime createdDate;
|
||||
private final String agentName;
|
||||
}
|
||||
|
||||
+58
@@ -23,11 +23,14 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PostPersist;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.agent.domain.Agent;
|
||||
import org.apache.fineract.portfolio.dds.party.domain.Party;
|
||||
import org.apache.fineract.portfolio.dds.voucher.DdsVoucherScope;
|
||||
@@ -87,6 +90,36 @@ public class PartyTransaction extends AbstractAuditableCustom {
|
||||
@Column(name = "balance_after_transaction", scale = 6, precision = 19, nullable = false)
|
||||
private BigDecimal balanceAfterTransaction;
|
||||
|
||||
/** The branch the line is booked at - the party's, or its agent's for a line with no party - as savings rows carry. */
|
||||
@Column(name = "office_id")
|
||||
private Long officeId;
|
||||
|
||||
/**
|
||||
* The business day the line was recorded, beside transactionDate - the day the money moved. They
|
||||
* differ when a collection is applied from a cashier's receipt days after it was collected.
|
||||
*/
|
||||
@Column(name = "submitted_on_date")
|
||||
private LocalDate submittedOnDate;
|
||||
|
||||
/**
|
||||
* The acc_gl_journal_entry.transaction_id the line was booked under, so a passbook line and its
|
||||
* journal can be found from each other. Null for a line that moves nothing on the books (a transfer
|
||||
* between two customers owed on the same account).
|
||||
*/
|
||||
@Column(name = "journal_transaction_id", length = 100)
|
||||
private String journalTransactionId;
|
||||
|
||||
/**
|
||||
* The line's own reference, unique across DDS, as a savings transaction carries ref_no: what a
|
||||
* statement, a complaint or a reconciliation quotes to name exactly one line.
|
||||
*/
|
||||
@Column(name = "ref_no", length = 40, nullable = false, unique = true)
|
||||
private String refNo;
|
||||
|
||||
/** The voucher the line was printed on - stamped by DdsVoucherService once the operation's voucher is raised. */
|
||||
@Column(name = "voucher_no", length = 30, insertable = false, updatable = false)
|
||||
private String voucherNo;
|
||||
|
||||
protected PartyTransaction() {
|
||||
//
|
||||
}
|
||||
@@ -108,6 +141,31 @@ public class PartyTransaction extends AbstractAuditableCustom {
|
||||
this.tokenNo = tokenNo;
|
||||
this.description = description;
|
||||
this.balanceAfterTransaction = balanceAfterTransaction;
|
||||
if (party != null && party.getOffice() != null) {
|
||||
this.officeId = party.getOffice().getId();
|
||||
} else if (agent != null && agent.getOffice() != null) {
|
||||
this.officeId = agent.getOffice().getId();
|
||||
} else if (agent != null && agent.getClient() != null && agent.getClient().getOffice() != null) {
|
||||
// An agent saved without a branch is still a client of one.
|
||||
this.officeId = agent.getClient().getOffice().getId();
|
||||
}
|
||||
this.submittedOnDate = DateUtils.getBusinessLocalDate();
|
||||
}
|
||||
|
||||
/** Names the journal this line was booked under, where the caller knows it better than the operation's last one. */
|
||||
public PartyTransaction linkJournal(final String transactionId) {
|
||||
this.journalTransactionId = transactionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@PrePersist
|
||||
private void linkToJournal() {
|
||||
if (this.refNo == null) {
|
||||
this.refNo = "DDST" + UUID.randomUUID().toString().replace("-", "").substring(0, 20).toUpperCase();
|
||||
}
|
||||
if (this.journalTransactionId == null) {
|
||||
this.journalTransactionId = DdsVoucherScope.lastLedgerTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+20
@@ -48,6 +48,8 @@ public final class DdsVoucherScope {
|
||||
private final Set<String> ledgerTransactionIds = new LinkedHashSet<>();
|
||||
private final List<Long> partyTransactionIds = new ArrayList<>();
|
||||
private final List<Line> extraLines = new ArrayList<>();
|
||||
/** The journal most recently written in this operation - what the next passbook line saved belongs to. */
|
||||
private String lastLedgerTransactionId;
|
||||
|
||||
private DdsVoucherScope() {}
|
||||
|
||||
@@ -72,6 +74,24 @@ public final class DdsVoucherScope {
|
||||
final DdsVoucherScope scope = CURRENT.get();
|
||||
if (scope != null && transactionId != null) {
|
||||
scope.ledgerTransactionIds.add(transactionId);
|
||||
scope.lastLedgerTransactionId = transactionId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The journal the passbook line being saved now belongs to: every DDS movement writes its journal
|
||||
* first and its passbook line straight after, so it is the last one written in this operation.
|
||||
*/
|
||||
public static String lastLedgerTransaction() {
|
||||
final DdsVoucherScope scope = CURRENT.get();
|
||||
return scope == null ? null : scope.lastLedgerTransactionId;
|
||||
}
|
||||
|
||||
/** For a movement that writes no journal, so its passbook line does not claim the previous one's. */
|
||||
public static void forgetLastLedgerTransaction() {
|
||||
final DdsVoucherScope scope = CURRENT.get();
|
||||
if (scope != null) {
|
||||
scope.lastLedgerTransactionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user