<?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: Nightly Software</title>
    <description>The latest articles on DEV Community by Nightly Software (nightlysoftware).</description>
    <link>https://dev.to/nightlysoftware</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%2Forganization%2Fprofile_image%2F15592%2Fcd648f95-b9cd-45f2-be9a-89154aa1f019.png</url>
      <title>DEV Community: Nightly Software</title>
      <link>https://dev.to/nightlysoftware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nightlysoftware"/>
    <language>en</language>
    <item>
      <title>Inventory expiry: how to test lots and FEFO before accepting the system</title>
      <dc:creator>Apocalix</dc:creator>
      <pubDate>Mon, 26 Oct 2026 03:40:23 +0000</pubDate>
      <link>https://dev.to/nightlysoftware/inventory-expiry-how-to-test-lots-and-fefo-before-accepting-the-system-3boa</link>
      <guid>https://dev.to/nightlysoftware/inventory-expiry-how-to-test-lots-and-fefo-before-accepting-the-system-3boa</guid>
      <description>&lt;p&gt;&lt;em&gt;Jonathan Perez · Nightly Software. Originally published October 7, 2026, and last updated on that date in the &lt;a href="https://nightlysoftware.com/en/blog/inventory-expiry-fefo" rel="noopener noreferrer"&gt;original guide&lt;/a&gt;. This cross-post was prepared with AI assistance to format and adapt that existing guide for DEV.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An expiry alert does not prove that an inventory system prevents shipping an expired lot. Before accepting the system, test the decision it makes, the quantities it changes and the evidence it preserves.&lt;/p&gt;

&lt;p&gt;This guide uses one complete, synthetic exercise. It tests FEFO picking, reservations, a quarantined return, partial release, expiry and a repeated event. You can run the same sequence against the software you already use. Passing it depends on the product, version, configuration and operating rules being tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  First define the dates and eligibility policy
&lt;/h2&gt;

&lt;p&gt;FEFO means first expired, first out, but the date driving a particular system must be checked. The original guide used Odoo 19 documentation, reviewed October 7, 2026: its &lt;a href="https://www.odoo.com/documentation/19.0/applications/inventory_and_mrp/inventory/product_management/product_tracking/expiration_dates.html" rel="noopener noreferrer"&gt;expiry documentation&lt;/a&gt; distinguishes expiry, best-before, removal and alert dates, and its &lt;a href="https://www.odoo.com/documentation/19.0/applications/inventory_and_mrp/inventory/shipping_receiving/removal_strategies/fefo.html" rel="noopener noreferrer"&gt;FEFO strategy&lt;/a&gt; prioritizes the assigned removal date.&lt;/p&gt;

&lt;p&gt;A warning and a picking block are separate controls. Write down these decisions before the demonstration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where the lot date comes from.&lt;/strong&gt; Compare it with the label or supporting documentation. Receiving an older lot today must not silently give it a new life.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which date orders picking.&lt;/strong&gt; Specify expiry or removal date and a tie-breaking rule. If removal is earlier than expiry, test that difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When eligibility ends.&lt;/strong&gt; Specify the date, time and time zone. Include any minimum remaining life required at delivery: a lot valid today may be unsuitable for a future shipment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What happens to missing or corrected dates.&lt;/strong&gt; Define review, permissions and a reason for the change. A missing date must not be silently treated as approval where the operation requires expiry tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agree these rules with receiving, picking and exception-approval staff. A column labelled “expiry” is not an acceptance test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep physical stock separate from available stock
&lt;/h2&gt;

&lt;p&gt;In this exercise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;available = on-hand - reserved - blocked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reserved and blocked are distinct groups. A piece cannot be deducted twice. If a reserved lot becomes blocked, review the commitment and move the affected pieces into the appropriate group.&lt;/p&gt;

&lt;p&gt;Quarantine is a block while a responsible person reviews returned goods or a discrepancy. The pieces may still be physically present. Quarantine does not mean disposal, and a warning alone does not move a quantity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test fixture: 100 pieces in three lots
&lt;/h2&gt;

&lt;p&gt;These are invented test data, not customer records or project results. Keep the exercise date at &lt;strong&gt;October 7, 2026&lt;/strong&gt; when reproducing it.&lt;/p&gt;

&lt;p&gt;The SKU is &lt;code&gt;INS-01&lt;/code&gt;, measured in pieces, in one warehouse. The exercise blocks a lot from &lt;strong&gt;00:00 on its expiry date, Mexico City time&lt;/strong&gt;. No additional minimum remaining life applies. Removal and expiry dates are identical, and there are no initial reservations. These are explicit test assumptions, not universal inventory or Odoo defaults.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lot&lt;/th&gt;
&lt;th&gt;Received&lt;/th&gt;
&lt;th&gt;Expiry and removal&lt;/th&gt;
&lt;th&gt;On-hand&lt;/th&gt;
&lt;th&gt;Blocked&lt;/th&gt;
&lt;th&gt;Available&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;V-01&lt;/td&gt;
&lt;td&gt;Oct 2, 2026&lt;/td&gt;
&lt;td&gt;Oct 7, 2026&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P-01&lt;/td&gt;
&lt;td&gt;Oct 6, 2026&lt;/td&gt;
&lt;td&gt;Oct 10, 2026&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C-01&lt;/td&gt;
&lt;td&gt;Oct 1, 2026&lt;/td&gt;
&lt;td&gt;Nov 1, 2026&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C00 total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Order &lt;code&gt;PED-45&lt;/code&gt; needs 45 pieces. Reserve &lt;strong&gt;30 from P-01, then 15 from C-01&lt;/strong&gt;. C-01 arrived earlier but expires later. V-01 is already blocked and must not be used to complete the order.&lt;/p&gt;

&lt;p&gt;Because expiry and removal dates coincide in this fixture, either date produces that order. If the dates differ in your configuration, repeat the test using the date that actually governs its strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the whole event sequence
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;On-hand&lt;/th&gt;
&lt;th&gt;Reserved&lt;/th&gt;
&lt;th&gt;Blocked&lt;/th&gt;
&lt;th&gt;Available&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;C00: initial state on Oct 7&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C01: reserve PED-45, using 30 P-01 + 15 C-01&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C02: physically dispatch the 45 reserved pieces, SAL-45&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C03: receive 5 returned P-01 pieces into quarantine, DEV-05&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C04: resend DEV-05 without another physical receipt&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C05: review releases 3; 2 remain blocked&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C06: Oct 10 arrives; the 3 released pieces expire&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C07: attempt to reserve 36; reject the request&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C08: authorized physical removal of 20 V-01 pieces for disposal&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check the ledger after each event, rather than only the final dashboard total:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reservation is not dispatch.&lt;/strong&gt; C01 changes the commitment, leaving 100 pieces physically present. C02 records their physical departure: 20 V-01 and 35 C-01 pieces remain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A return is not immediate availability.&lt;/strong&gt; C03 receives five pieces, but all five enter quarantine. On-hand rises to 60; availability stays at 35.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A repeated event is not another return.&lt;/strong&gt; C04 represents the same &lt;code&gt;DEV-05&lt;/code&gt; receipt. It must retain the existing reference and balance, adding no pieces. A different return needs its own reference and evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial release needs an audit trail.&lt;/strong&gt; C05 records who released three pieces, why and when, and why two remain blocked. The three released pieces are still eligible on October 7, so availability becomes 38.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time can change eligibility without changing physical stock.&lt;/strong&gt; On October 10, those three pieces expire. Availability returns to 35. The two already in quarantine are not deducted a second time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disposal needs a physical movement.&lt;/strong&gt; C08 reduces on-hand and blocked stock by 20 each. It does not change the 35 available pieces. Keep the lot history; deleting a lot is not a substitute for recording its removal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final reconciliation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 initial - 45 dispatched + 5 returned - 20 removed = 40 on-hand
40 on-hand - 0 reserved - 5 blocked = 35 available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those 40 pieces are &lt;strong&gt;35 C-01 pieces available and five P-01 pieces blocked&lt;/strong&gt;. If the totals differ, keep the discrepancy open even if the screens look correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the return evidence
&lt;/h2&gt;

&lt;p&gt;Record the product, lot, quantity, unit, original dispatch document and return reference before deciding what can be released. Changing an order status must neither recreate the goods nor renew their expiry date.&lt;/p&gt;

&lt;p&gt;Under this exercise’s policy, the responsible person reviews the returned goods before releasing some, retaining a block or authorizing a physical removal. Preserve the original dispatch so the remaining distribution of that lot can be traced.&lt;/p&gt;

&lt;p&gt;If your operation uses a different return policy, replace the assumptions in writing before running the tests. The fixture is a proposed acceptance model, not a statement that every inventory product behaves this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the exercise into an acceptance checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Evidence to inspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Receive lots in a different order from their expiry dates&lt;/td&gt;
&lt;td&gt;The eligible lot with earlier expiry/removal is selected first, even when received later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show an alert without relying on it as a block&lt;/td&gt;
&lt;td&gt;An expired lot cannot be reserved or picked under the agreed policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put removal before expiry&lt;/td&gt;
&lt;td&gt;Priority and eligibility use the actual configured dates for the product and customer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross the exact expiry boundary&lt;/td&gt;
&lt;td&gt;Check before and from the cutoff, including a reservation made the day before.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Omit or correct a lot date&lt;/td&gt;
&lt;td&gt;Visible review, authorized changes and a reason; preserve date history.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split one order across lots&lt;/td&gt;
&lt;td&gt;Quantities and documents retain the actual lot for each dispatch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Receive a return and release only part&lt;/td&gt;
&lt;td&gt;Only received goods increase on-hand; only approved, eligible goods increase availability.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Send the same event twice&lt;/td&gt;
&lt;td&gt;One receipt or dispatch, with the original reference and unchanged balance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request more than is available&lt;/td&gt;
&lt;td&gt;An explicit rejection or pending state; expired goods do not fill the gap.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove goods for disposal&lt;/td&gt;
&lt;td&gt;Authorized movement, reconciled balances and accessible lot history.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run the sequence with the people who will receive and pick, on the intended devices. Keep event references, expected balances, screenshots and discrepancies. Compare the physical label with the lot selected in the system. After a correction, repeat the affected steps and verify their balances again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure first, then decide whether to extend
&lt;/h2&gt;

&lt;p&gt;Start with the system you already use: lot tracking, dates, removal strategy, permissions and blocking rules. If configuration passes the agreed tests, you may not need another system.&lt;/p&gt;

&lt;p&gt;If sales, the warehouse and a customer portal show different balances, identify the missing integration or extension before replacing everything. The decision should follow demonstrated rules and reconciled evidence, rather than the presence of an expiry screen.&lt;/p&gt;

&lt;p&gt;This is an operational acceptance exercise. It does not certify sanitary, tax or regulatory compliance, and it makes no claim that Nightly delivered these rules for a previous customer. The original publication date, source-review date and synthetic exercise dates remain October 7, 2026.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>architecture</category>
      <category>database</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
