<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Salil Harit</title>
    <description>The latest articles on DEV Community by Salil Harit (@salil_harit_0b795563c34ec).</description>
    <link>https://dev.to/salil_harit_0b795563c34ec</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4183955%2Ff330ea5c-6a6e-4755-81f6-e35fd9dcf48d.jpg</url>
      <title>DEV Community: Salil Harit</title>
      <link>https://dev.to/salil_harit_0b795563c34ec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/salil_harit_0b795563c34ec"/>
    <language>en</language>
    <item>
      <title>Testing the business meaning of a Flutter staffing dashboard</title>
      <dc:creator>Salil Harit</dc:creator>
      <pubDate>Wed, 14 Oct 2026 10:57:55 +0000</pubDate>
      <link>https://dev.to/salil_harit_0b795563c34ec/testing-the-business-meaning-of-a-flutter-staffing-dashboard-5fd6</link>
      <guid>https://dev.to/salil_harit_0b795563c34ec/testing-the-business-meaning-of-a-flutter-staffing-dashboard-5fd6</guid>
      <description>&lt;p&gt;A staffing dashboard helps an operator decide where attention is needed. Its usefulness depends on what its labels mean and whether the operator can act on them.&lt;/p&gt;

&lt;p&gt;In SEC ERP's mobile work, I shaped tests around those decisions: distinguishing assignment from presence, publishing the right duty result, and keeping staffing actions usable on a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the facts behind the labels
&lt;/h2&gt;

&lt;p&gt;An assignment, an attendance record, and an active duty session represent different facts. The roster needs to preserve those distinctions.&lt;/p&gt;

&lt;p&gt;One regression fixture includes an assigned guard with null attendance and duty evidence. This exercises a realistic incomplete record. A perfectly populated fixture would miss the question that matters: what does the interface show when presence evidence is unavailable?&lt;/p&gt;

&lt;p&gt;The read-only view receives no assignment callback, and its test verifies that assignment controls are absent. Action availability follows the capability supplied to the view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify what a successful response means
&lt;/h2&gt;

&lt;p&gt;For duty activation, a non-null response is too weak an assertion. The acknowledgment must correspond to the guard and assignment that initiated the request.&lt;/p&gt;

&lt;p&gt;Tests change returned identity, company, facility, deployment, shift, date, status, and required evidence. An incorrect result must not publish confirmed duty.&lt;/p&gt;

&lt;p&gt;A separate test accepts cloud activation and then fails device persistence. It expects confirmed duty to survive with a separate mirror-failure outcome. That tests the partial success the architecture is designed to handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exercise the action inside its real layout
&lt;/h2&gt;

&lt;p&gt;The roster presents posts and shifts as a grid. The assignment action must stay reachable and retain the selected cell's identity.&lt;/p&gt;

&lt;p&gt;The widget test uses the application theme, a 360-by-800 viewport, and enlarged text. It scrolls to a target cell, taps its assignment button, and verifies the exact post and shift delivered to the callback.&lt;/p&gt;

&lt;p&gt;This combines layout and behavior in a useful scenario: an operator can reach the action and act on the intended staffing gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test changes during requests
&lt;/h2&gt;

&lt;p&gt;Other regressions introduce logout, disposal, a newer duty selection, and listener-driven changes before or during publication. Controllable service completion reproduces each ordering precisely.&lt;/p&gt;

&lt;p&gt;The assertion is that newer state survives while obsolete work cannot publish or persist. This protects the actual user journey beyond the initial screen render.&lt;/p&gt;

&lt;p&gt;I chose these cases by the decisions they protect: interpreting staffing evidence, accepting duty confirmation, preserving current state, and reaching the correct assignment action. Device and operator checks answer additional questions such as location behavior and field usability.&lt;/p&gt;

&lt;p&gt;The testing strategy gives architectural decisions an executable form. Each case checks a fact the dashboard communicates or an action its users depend on.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>testing</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Keeping Flutter dashboards correct when async requests finish out of order</title>
      <dc:creator>Salil Harit</dc:creator>
      <pubDate>Wed, 14 Oct 2026 10:56:58 +0000</pubDate>
      <link>https://dev.to/salil_harit_0b795563c34ec/keeping-flutter-dashboards-correct-when-async-requests-finish-out-of-order-2op8</link>
      <guid>https://dev.to/salil_harit_0b795563c34ec/keeping-flutter-dashboards-correct-when-async-requests-finish-out-of-order-2op8</guid>
      <description>&lt;p&gt;A guard starts duty while an older dashboard request is still loading. Activation succeeds and the dashboard updates. Then the older response arrives and replaces the newer state.&lt;/p&gt;

&lt;p&gt;The network request succeeded, but the product now shows an obsolete answer. In SEC ERP's mobile app, I addressed this with authentication generations and duty revisions in shared state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give results an owning context
&lt;/h2&gt;

&lt;p&gt;A mounted check answers whether a widget still exists. Operational state also needs to know whether a result belongs to the current account.&lt;/p&gt;

&lt;p&gt;Async work captures the provider's authentication generation before waiting. Before publication, it checks disposal, session teardown, and whether the generation still matches. An earlier response cannot restore old identity or dashboard data after that session boundary changes.&lt;/p&gt;

&lt;p&gt;The request may finish remotely. The check controls whether its result becomes current client state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Track changes inside the same account
&lt;/h2&gt;

&lt;p&gt;An account can stay signed in while the selected duty changes. Authentication generation alone cannot distinguish those situations.&lt;/p&gt;

&lt;p&gt;Duty activation therefore captures a duty revision too. Publication requires both values to match. The returned record must also match the requested guard, company, site, deployment, shift, and date, and contain required activation evidence.&lt;/p&gt;

&lt;p&gt;Ordering checks and record validation complement each other. A response can arrive at the right time and still describe the wrong operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat notification as a control boundary
&lt;/h2&gt;

&lt;p&gt;Publishing state invokes synchronous listeners. A listener can select another duty or invalidate the account before notification returns.&lt;/p&gt;

&lt;p&gt;The activation path rechecks its assumptions after notification, before local persistence, and again after persistence. This prevents an obsolete selection from being mirrored merely because its cloud request succeeded.&lt;/p&gt;

&lt;p&gt;The principle extends beyond await: whenever control passes to another owner, ask what could change before control returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make races deterministic
&lt;/h2&gt;

&lt;p&gt;The regression tests use controllable service completion. They start activation, introduce logout, disposal, or a newer duty, and then deliver the acknowledgment. Another case replaces duty from a listener during publication.&lt;/p&gt;

&lt;p&gt;Assertions verify the operational effect: obsolete work must not publish or persist, and the newer selection must survive.&lt;/p&gt;

&lt;p&gt;Session generations establish account continuity. Domain revisions establish operation continuity. Acknowledgment validation establishes identity. Together, they keep the dashboard aligned with the current operational decision as requests finish in whatever order the network allows.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>architecture</category>
      <category>testing</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Designing a SQLite outbox for field operations in Flutter</title>
      <dc:creator>Salil Harit</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:52:16 +0000</pubDate>
      <link>https://dev.to/salil_harit_0b795563c34ec/designing-a-sqlite-outbox-for-field-operations-in-flutter-6a9</link>
      <guid>https://dev.to/salil_harit_0b795563c34ec/designing-a-sqlite-outbox-for-field-operations-in-flutter-6a9</guid>
      <description>&lt;p&gt;When a field worker enters operational information, the valuable part is the work they have already done. Connectivity should not be the only thing keeping that work alive.&lt;/p&gt;

&lt;p&gt;In SEC ERP's mobile app, I designed a buffered-write path around that requirement. Flutter handles the interaction, SQLite preserves the request, and Supabase processes the cloud write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist intent before transport
&lt;/h2&gt;

&lt;p&gt;The buffer inserts a queue entry before sending. It records the originating account, operation, payload, creation time, and status. If connectivity is unavailable, the request stays pending. A conditional claim moves an eligible entry to IN_FLIGHT before processing.&lt;/p&gt;

&lt;p&gt;Compared with keeping retries in memory, this adds persistence and queue management. I chose that complexity because recovery needs a record that survives beyond the request's lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make retries an architectural decision
&lt;/h2&gt;

&lt;p&gt;A response can disappear after the server accepts an insertion. Sending again can create another record. Automatically retrying every error would turn a network problem into a data-quality problem.&lt;/p&gt;

&lt;p&gt;The buffer classifies failures: recognized rejections are retained for correction, uncertain INSERT failures require review, and other eligible failures enter RETRYING.&lt;/p&gt;

&lt;p&gt;The tradeoff is deliberate. Some requests need review instead of automatic completion, but entered work remains available. Where automatic insertion replay is required, stable request identities and server deduplication provide a stronger contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attach recovery to the originating account
&lt;/h2&gt;

&lt;p&gt;Queue entries carry an owner identifier. Reads and status updates use that owner, and processing checks account continuity around claims and transport.&lt;/p&gt;

&lt;p&gt;This makes ownership part of persistence. Switching accounts does not transfer the previous account's pending work to the new user.&lt;/p&gt;

&lt;p&gt;Expired claims also have explicit recovery rules: UPDATE operations return to RETRYING, while other expired in-flight operations require review. Recovery follows the operation type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate confirmation from caching
&lt;/h2&gt;

&lt;p&gt;Duty activation shows why one success flag is insufficient. Supabase can confirm activation while the device fails to cache the returned record.&lt;/p&gt;

&lt;p&gt;The provider validates and publishes the confirmed duty, then attempts local mirroring. A mirror failure is reported separately while confirmed duty remains in live state. Recovery can focus on device storage rather than repeating an already-confirmed action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate the failure boundaries
&lt;/h2&gt;

&lt;p&gt;Regression cases challenge zero-row updates, wrong returned targets, rejected activation, and accepted activation followed by local persistence failure.&lt;/p&gt;

&lt;p&gt;Each protects a business meaning across a failure boundary. The result is an architecture where field work has a durable home, retries have explicit rules, and cloud confirmation remains distinct from device storage.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>architecture</category>
      <category>mobile</category>
      <category>database</category>
    </item>
    <item>
      <title>Building a bulk import pipeline for fixed-asset accounting in Spring Boot</title>
      <dc:creator>Salil Harit</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:14:10 +0000</pubDate>
      <link>https://dev.to/salil_harit_0b795563c34ec/building-a-bulk-import-pipeline-for-fixed-asset-accounting-in-spring-boot-4jf8</link>
      <guid>https://dev.to/salil_harit_0b795563c34ec/building-a-bulk-import-pipeline-for-fixed-asset-accounting-in-spring-boot-4jf8</guid>
      <description>&lt;p&gt;In fixed-asset accounting, importing a spreadsheet means reconstructing a business model: who purchased an asset, what it cost, which accounting rules apply, and which physical units the business now owns. If those relationships are wrong, the consequences travel into custody records, depreciation schedules, and disposal reports.&lt;/p&gt;

&lt;p&gt;I built the import pipeline in FAMS, a fixed asset management system using Flutter, Spring Boot, and PostgreSQL. The business problem was to bring existing asset registers into the application without making accountants manually recreate every vendor, asset definition, and purchase. The engineering problem was to preserve accounting relationships while handling validation, partial failures, and growing datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the asset lifecycle
&lt;/h2&gt;

&lt;p&gt;The data model separates three responsibilities. A purchase header holds invoice-level information. Its items hold asset quantities and financial details. Individual labels identify the physical units that continue through allocation, verification, depreciation, and disposal.&lt;/p&gt;

&lt;p&gt;Consider an invoice for ten identical laptops. The invoice is one financial transaction, but the laptops can go to different employees and eventually have different disposal histories. Separating financial ownership from physical identity lets those histories evolve without rewriting the purchase.&lt;/p&gt;

&lt;p&gt;That model shapes ingestion. The import must create connected domain records, rather than copy spreadsheet cells into one flat table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put accounting decisions behind a service boundary
&lt;/h2&gt;

&lt;p&gt;The architecture follows a clear path:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Excel → Flutter parsing and validation → REST bulk endpoint → Spring Boot services → JPA repositories → PostgreSQL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Flutter interprets the workbook and presents validation feedback. The backend owns reference resolution, dependency checks, and persistence. Controllers coordinate requests and responses; services implement the business operation; repositories handle relational access.&lt;/p&gt;

&lt;p&gt;This keeps accounting behavior reusable across clients. Changing a screen should not change how a purchase connects to its asset definitions or financial rules. It also gives debugging a useful structure: distinguish a parsing problem from a reference-resolution problem or a database write failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolve dependencies in business order
&lt;/h2&gt;

&lt;p&gt;Spreadsheet users work with names. Database relationships work with identifiers. Bridging that gap is a substantial part of the pipeline.&lt;/p&gt;

&lt;p&gt;For an asset item, the service resolves its block, then its sub-block using the resolved parent. It resolves the depreciation rule using both its name and method, and then finds or creates the asset definition with those references. Vendor resolution similarly connects the purchase to the appropriate company-specific master.&lt;/p&gt;

&lt;p&gt;The order matters. A sub-block needs its block identity; an asset definition needs the accounting references that describe it. Resolving those prerequisites inside the import reduces the separate setup work required before an accountant can load a register.&lt;/p&gt;

&lt;p&gt;Company identity is included in relevant lookups. Two companies can use the same asset or vendor name without those names implying the same record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache repeated resolutions within the import
&lt;/h2&gt;

&lt;p&gt;A register often repeats the same vendor, asset definition, unit, and depreciation rule. Looking up each reference again for every item repeats work without adding business value.&lt;/p&gt;

&lt;p&gt;The bulk request creates an import context with maps for these resolutions. Keys reflect the domain: company and vendor name; company, block, and sub-block name; or company, rule name, and depreciation method. When the service creates a vendor or asset definition, it remembers that result for subsequent entries.&lt;/p&gt;

&lt;p&gt;This makes reuse explicit at the application layer, including across separate transaction calls. The cache lifetime matches the import operation, keeping the optimization close to the workflow it serves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make one purchase the unit of consistency
&lt;/h2&gt;

&lt;p&gt;The bulk controller processes submitted transactions individually and returns an outcome for each. Each service call uses a database transaction to create the purchase and its related items together.&lt;/p&gt;

&lt;p&gt;This boundary serves two business needs. A failed item should not leave its purchase half-created. At the same time, one bad purchase should not undo other valid purchases in the upload. The controller records each success or failure and continues, giving the interface enough information to direct corrections.&lt;/p&gt;

&lt;p&gt;The response position identifies a submitted transaction; the frontend maps that outcome back to the workbook. This distinction matters when several spreadsheet entries belong to one business transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bound Hibernate's managed state
&lt;/h2&gt;

&lt;p&gt;Within a large purchase, JPA can accumulate managed entities as items are created. I added a flush-and-clear step every 20 items, then reacquired the purchase before continuing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified excerpt inside the transactional item loop.&lt;/span&gt;
&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;persist&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;purchase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;find&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Purchase&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purchaseId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flushing synchronizes pending writes; clearing releases managed state and detaches the parent too, which explains the reacquisition. The purchase transaction remains the consistency boundary. This addresses persistence-context growth while preserving the business operation's transaction semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The pipeline was client-tested with 956 records, and I personally tested imports up to 10,000 records.&lt;/p&gt;

&lt;p&gt;The architecture connects those workload results to a practical outcome: accountants can load existing registers through a familiar spreadsheet workflow, while the application constructs the relationships needed for the rest of the asset lifecycle. Ordered dependency resolution removes setup friction, transactional writes preserve connected purchases, cached lookups reduce repeated work, and per-entry feedback makes corrections manageable.&lt;/p&gt;

</description>
      <category>java</category>
      <category>backend</category>
      <category>postgres</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
