Pharmacy POS Configuration: Batch Tracking & Automatic Expiry Alerts
T
Kembali ke Blog

Pharmacy POS Configuration: Batch Tracking & Automatic Expiry Alerts

Tutorial
Tim Pilar Inovasi 27 Jul 2026 16 min baca 3,549 kata 189
Learn how to configure a pharmacy POS system with batch tracking and automatic expiry alerts. This article covers technical implementation, code examples, and best practices for efficiency and safety.

Pharmacy inventory management is one of the most crucial and complex aspects of pharmacy or hospital operations. Without an adequate system, the risk of errors in handling medications, especially regarding batch numbers and expiration dates, is very high. These errors can not only cause financial losses due to unsold or destroyed medications, but more importantly, can endanger patient safety. Standard pharmacy Point of Sales (POS) systems often focus only on basic sales transactions, ignoring the vital need for batch tracking and proactive expiration alerts. As a result, operations managers and IT teams often struggle with dead stock challenges, complicated product recalls, and regulatory non-compliance. This article will guide you, hospital IT managers, clinic owners, operations managers, and decision-makers, through concrete steps to configure your pharmacy POS system to have advanced batch tracking and expiration alert capabilities. We will discuss basic concepts, technical implementation details with code examples, error handling scenarios, best practices, and answer common questions to ensure your system is not only efficient but also safe and regulatory compliant.

Basic Concepts of Batch Tracking and Expiration Alerts

Batch tracking is a method of identifying and recording each unit of pharmaceutical product based on its production batch number. Each batch represents a group of products manufactured in one cycle or under specific conditions, and typically has the same production date and expiration date. The importance of batch tracking in the pharmaceutical industry cannot be overstated. Regulations such as Permenkes No. 73 of 2016 concerning Pharmaceutical Service Standards in Pharmacies implicitly require pharmacies to have an accurate stock management system, including identification of products approaching or past expiration. Moreover, batch tracking enables pharmacies to perform product recalls efficiently and on target if quality or safety issues occur in a specific batch, minimizing risks to patients and reputational losses.

Meanwhile, expiration alerts are a crucial feature that proactively notifies users about medications that are about to expire or have already expired. Without an automatic alert system, pharmacies risk accumulating stock of medications that cannot be sold or even accidentally dispensing expired medications to patients. A study shows that losses due to expired medications in pharmacies can reach 10-15% of total inventory value if not managed properly. With a proper alert system, pharmacies can take preventive actions such as running special promotions for medications approaching expiration or returning them to distributors (if possible), thereby reducing waste and increasing profitability.

To implement these two concepts, every pharmaceutical item in the system must have complete batch-related data. Key data that must be recorded for each batch includes: item_id (pharmaceutical product ID), batch_number (unique batch number), manufacture_date (production date), expiry_date (expiration date), and quantity (number of units in that batch). Additionally, it is important to adopt the First-Expired, First-Out (FEFO) principle in the sales or distribution process. The FEFO principle ensures that medications with the nearest expiration date are sold or used first, significantly reducing the risk of accumulating expired medications and ensuring patient safety. Although FIFO (First-In, First-Out) is often used in general stock management, FEFO is far more relevant and crucial for pharmaceutical products.

Effective implementation of batch tracking and expiration alerts will directly support compliance with pharmaceutical service standards, improve operational efficiency, and most importantly, protect patients from the potential dangers of unsuitable medications. This is a strategic investment that provides long-term value for modern pharmacies.

Pharmacy POS System Implementation Details

To build a pharmacy POS system with effective batch tracking and expiration alerts, we need a robust database structure and a well-planned system architecture. In this guide, we will use a popular and reliable technology stack: Laravel 11.x backend with PHP 8.3+ and PostgreSQL 16 database. Laravel provides a rich ecosystem for web application development, including ORM (Eloquent) features that facilitate database interaction, as well as a scheduler for scheduled tasks.

The database structure is the main foundation. We will need several core tables: products, batches, and inventory_transactions. The products table will store basic information for each medication or pharmaceutical product, such as id, name, sku (Stock Keeping Unit), unit_price, and a boolean flag is_batch_tracked to indicate whether the product requires batch tracking. The batches table is the core of the tracking system, storing specific details of each batch: id, product_id (foreign key to the products table), batch_number (unique string), manufacture_date, expiry_date, and initial_quantity. It is important to add a unique index on (product_id, batch_number) to prevent data duplication.

The inventory_transactions table will record every stock movement, both incoming (receipts) and outgoing (sales, returns, disposal). The required columns include: id, product_id, batch_id (foreign key to the batches table), transaction_type (ENUM: 'IN', 'OUT', 'ADJUSTMENT'), quantity, transaction_date, and reference_id (for example, the purchase or sales invoice ID). With a transaction table, we can calculate the current stock for each batch dynamically, or store the current stock in a separate stock_levels table for better performance. This approach ensures full auditability and integrity of inventory data.

The operational workflow will involve several key stages. When receiving goods, pharmacy staff must input the details of each batch received, including the batch number, production date, expiry date, and number of units. The system will record this as an 'IN' transaction in the inventory_transactions table. When selling, the system must automatically identify which batch should be issued based on the FEFO principle. This means the system will look for the batch with the nearest expiry date that still has stock. Once the batch is identified, the quantity sold will be recorded as an 'OUT' transaction, reducing that batch's stock. For expiry warnings, we will utilize the Laravel Task Scheduler to run a command periodically (for example, daily) that will scan the batches table and identify products that are about to expire or have already expired, then trigger a notification.

Although this article focuses on the backend, it should be noted that the user interface (frontend) can be built using modern frameworks such as Vue.js 3 or React 18 to provide a responsive and intuitive user experience. Integration with other modules such as sales reports, accounting, and even electronic medical record systems (SIMRS) can be done through a RESTful API, ensuring a connected and efficient ecosystem. Interoperability standards such as FHIR R4 or HL7 v2.5.1 can be considered if there is a need for bridging with external health systems, although for an internal POS, a simple RESTful API is sufficient.

Implementation Code Example

This section will present concrete code examples to illustrate how the database structure and expiry warning functionality can be implemented using Laravel 11.x and PostgreSQL 16. This code is designed to be directly runnable and to serve as a basis for further development.

First, we will create a database migration for the batches table. This migration will define the table schema needed to store batch information. Run the following command in your terminal:

php artisan make:migration create_batches_table

Then, edit the newly created migration file (usually at `database/migrations/YYYY_MM_DD_HHMMSS_create_batches_table.php`) with the following code:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('batches', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained('products')->onDelete('cascade');
$table->string('batch_number')->index();
$table->date('manufacture_date');
$table->date('expiry_date');
$table->integer('quantity'); // Initial quantity for the batch
$table->integer('current_quantity'); // Current available quantity
$table->timestamps();

// Ensure unique batch number per product
$table->unique(['product_id', 'batch_number']);
$table->index(['expiry_date', 'current_quantity']); // For FEFO queries
});

// Add a current_quantity column to products for easy stock sum
Schema::table('products', function (Blueprint $table) {
if (!Schema::hasColumn('products', 'is_batch_tracked')) {
$table->boolean('is_batch_tracked')->default(false);
}
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('batches');
Schema::table('products', function (Blueprint $table) {
if (Schema::hasColumn('products', 'is_batch_tracked')) {
$table->dropColumn('is_batch_tracked');
}
});
}
};

After this migration is run (`php artisan migrate`), you will have a batches table ready to store product batch data. The current_quantity column is added for performance optimization in stock retrieval, and is_batch_tracked on the products table allows flexibility for products that do not require batch tracking. The index on expiry_date and current_quantity is very important for efficient FEFO queries.

Second, we will create a Laravel command that will run on a schedule to check for batches that are about to expire. Create a new command with:

php artisan make:command CheckExpiredBatches

Then, edit the newly created command file (`app/Console/Commands/CheckExpiredBatches.php`) with the following code. This code will look for batches that expire within the next 30 days or that have already expired, then log them or send a notification.

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Batch;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail; // Example for sending email notifications

class CheckExpiredBatches extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'batches:check-expired {--days=30 : Number of days before expiry to alert}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Checks for batches that are expired or expiring soon and sends alerts.';

/**
* Execute the console command.
*/
public function handle()
{
$daysThreshold = (int) $this->option('days');
$alertDate = Carbon::now()->addDays($daysThreshold);

$expiringBatches = Batch::where('expiry_date', '<=', $alertDate->toDateString())
->where('current_quantity', '>', 0)
->with('product') // Eager load product info
->get();

if ($expiringBatches->isEmpty()) {
$this->info('No expiring or expired batches found within the next ' . $daysThreshold . ' days.');
Log::info('No expiring or expired batches found within the next ' . $daysThreshold . ' days.');
return Command::SUCCESS;
}

$this->warn('Found ' . $expiringBatches->count() . ' batches expiring or expired soon.');
Log::warning('Found ' . $expiringBatches->count() . ' batches expiring or expired soon.');

foreach ($expiringBatches as $batch) {
$status = (Carbon::parse($batch->expiry_date)->isPast()) ? 'EXPIRED' : 'EXPIRING SOON';
$message = "Product: {$batch->product->name} (SKU: {$batch->product->sku}), Batch: {$batch->batch_number}, Expiry Date: {$batch->expiry_date}, Status: {$status}, Quantity: {$batch->current_quantity}.";

$this->line($message);
Log::alert($message);

// Example: Send email notification
// Mail::raw($message, function ($mail) {
// $mail->to('operations@yourpharmacy.com')->subject('Expired/Expiring Batch Alert');
// });
}

return Command::SUCCESS;
}
}

Finally, register this command in the Laravel scheduler. Open the `app/Console/Kernel.php` file and add the following lines in the `schedule` method:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
$schedule->command('batches:check-expired --days=30')->dailyAt('02:00');
$schedule->command('batches:check-expired --days=7')->weeklyOn(1, '03:00'); // Every Monday
$schedule->command('batches:check-expired --days=0')->dailyAt('04:00'); // Already expired
}

With this configuration, the system will automatically check for batches that will expire every day at 02:00 (for the next 30 days), every Monday at 03:00 (for the next 7 days), and every day at 04:00 (for those already expired). Notifications can be customized to be sent via email, SMS, or displayed on the admin dashboard. Make sure to configure your server's cron job to run `php artisan schedule:run` every minute.

Data and Error Handling

Data integrity is key in a pharmacy POS system, especially when dealing with sensitive batch and expiry information. Data input errors can have serious impacts on operations and patient safety. Therefore, a comprehensive data and error handling strategy must be implemented.

First, let's look at a realistic JSON payload example for the product receiving process, which involves multiple batches at once. This is a representation of data that might be sent from the user interface or supplier system to your backend API:

{
"product_sku": "PARA500",
"product_name": "Paracetamol 500mg",
"batches": [
{
"batch_number": "BAT-20231015-A",
"manufacture_date": "2023-10-15",
"expiry_date": "2025-10-15",
"quantity": 100
},
{
"batch_number": "BAT-20231101-B",
"manufacture_date": "2023-11-01",
"expiry_date": "2025-11-01",
"quantity": 50
},
{
"batch_number": "BAT-20231015-A",
"manufacture_date": "2023-10-15",
"expiry_date": "2025-10-15",
"quantity": 75
}
]
}

In the payload above, there is a deliberate error: the batch number "BAT-20231015-A" appears twice for the same product. The system must be able to detect and reject such input. An example error message that the API might return if validation fails is:

{
"error": "Validation Failed",
"message": "The provided data contains invalid or duplicate entries.",
"details": {
"batches.0.batch_number": ["The batch number 'BAT-20231015-A' for product 'PARA500' already exists or is duplicated within this request."],
"batches.1.expiry_date": ["The expiry date must be after the manufacture date."]
}
}

Effective error handling starts with strict input validation, both on the client side (frontend) and, most importantly, on the server side (backend). Laravel provides powerful validation features. You should apply validation rules to each field: ensure batch_number is unique per product_id, manufacture_date and expiry_date are valid date formats, and expiry_date is always after manufacture_date. Additionally, ensure quantity is a positive number. Server-side validation is the last and most important defense against invalid data.

In addition to validation, database constraints also play an important role. As shown in the previous migration example, adding a unique index on (product_id, batch_number) in the batches table will prevent data duplication at the database level, even if application validation is missed. This is a very strong layer of data security.

When an error occurs (for example, validation failure, database connection failure, or unexpected error), the system must:

  1. Log the Error: Use a logging system such as Monolog (built into Laravel) or integration with external services such as Sentry or the ELK Stack. Logs must include error details, time of occurrence, and related context (for example, the request payload).
  2. Provide Clear Feedback: Error messages returned to the user or calling system must be informative, indicating what went wrong and how to fix it. Avoid generic error messages.
  3. Rollback Transactions: If an operation involves multiple steps (for example, creating a product and then several batches), ensure all changes are rolled back if any step fails. Laravel provides a database transaction mechanism for this.
It is also important to consider integration scenarios. If your POS system communicates with SIMRS or another system (for example, for stock data synchronization), ensure your API is resilient to errors from external systems and has a retry or fallback mechanism. Using standards such as FHIR or HL7 for patient and prescription data integration can reduce complexity, but for inventory data, a well-designed RESTful API is sufficient, as long as it has robust error handling.

Best Practices

To ensure your pharmacy POS system runs optimally with batch tracking and expiry warnings, here is a set of best practices you should implement:

  1. Strictly Implement the FEFO (First-Expired, First-Out) Principle: Prioritize dispensing medications with the nearest expiration date. This is a fundamental principle in pharmaceutical management that minimizes losses due to expiration and ensures patients receive products with maximum shelf life. Configure your system to automatically select FEFO batches during sales or stock dispensing.
  2. Automate Expiry Warnings with Multiple Thresholds: Do not rely on only one warning. Configure the system to send notifications at multiple stages, for example, 6 months, 3 months, 1 month before expiration, and immediately after expiration. This provides sufficient time for proactive actions such as promotions, transfers, or returns to distributors.
  3. Conduct Regular Physical Stock Audits and Reconciliation: Even if the digital system is accurate, discrepancies between physical stock and system data can always occur due to human error, loss, or theft. Conduct regular physical stock opname (for example, monthly or quarterly) and compare it with system data, then make the necessary adjustments.
  4. Comprehensive and Continuous User Training: Ensure all staff who interact with the system, especially in the receiving and sales processes, fully understand the importance of accurate batch and expiry data input. Regular training sessions and standard operating procedures (SOPs) are essential to maintain data quality.
  5. Routinely Perform Data Backup and Disaster Recovery Plans (DRP): Inventory and transaction data are vital assets. Implement a daily automated database backup schedule and store backups in a separate location (off-site). Also have a clear disaster recovery plan to anticipate system failures or unexpected disasters.
  6. Implement Layered Data Security: Protect your POS system from unauthorized access. Use multi-factor authentication, role-based access control, and encryption of sensitive data, both in transit and at rest. This is very important to maintain the confidentiality and integrity of pharmaceutical data.
  7. Utilize Analytical Reports and Dashboards: Develop robust reports to monitor stock movement, identify slow-moving products, analyze expiration patterns, and project stock needs. Visual dashboards can provide a quick overview of your inventory health and assist in strategic decision-making.
  8. Compliance with Regulations and Industry Standards: Ensure your system always complies with the latest regulations from the Ministry of Health of the Republic of Indonesia, BPOM, and other pharmaceutical practice standards. This includes the ability to generate reports required by regulators and maintain a complete audit trail for every batch transaction.

FAQ

Q1: Why is batch tracking crucial for pharmacy operations?
Batch tracking is crucial for several key reasons. First, it ensures patient safety by enabling rapid and accurate identification of products that need to be recalled if there are quality issues or adverse side effects. Second, it helps pharmacies comply with government regulations, such as those set by BPOM and the Ministry of Health, which often require a complete audit trail for every pharmaceutical product. Finally, it enables more efficient stock management through the application of the FEFO principle.
Q2: How does the system determine the priority for selling goods if there are multiple batches of the same product?
A good system will use the First-Expired, First-Out (FEFO) principle to determine sales priority. This means the system will automatically select the batch with the nearest expiration date that still has stock to be sold first. If there are two batches with the same expiration date, the system may switch to the First-In, First-Out (FIFO) principle or select the batch with the smallest quantity to deplete it. The main goal is to minimize the risk of expired drugs and reduce waste.
Q3: What types of expiration alerts can be configured in a pharmacy POS system?
An advanced pharmacy POS system can be configured to issue various types of alerts. These include early warnings (for example, 6 months, 3 months, or 1 month before the expiration date) to allow proactive action. There are also alerts for products that have already expired, which typically trigger quarantine or disposal actions. In addition, some systems can also provide minimum stock alerts for certain products, although this is separate from expiration alerts.
Q4: Can a pharmacy POS system with batch tracking be integrated with other systems such as SIMRS or E-Prescription?
Yes, integration is one of the main strengths of modern systems. This pharmacy POS system can be integrated with a Hospital Management Information System (SIMRS) to synchronize inventory data, or with an E-Prescription system to process prescriptions digitally and automatically reduce stock. This integration is usually done through RESTful APIs, or for more complex healthcare environments, through interoperability standards such as FHIR (Fast Healthcare Interoperability Resources) or HL7, ensuring seamless and efficient data exchange between systems.
Q5: How long does it take to implement a pharmacy POS system with batch tracking and expired alert features?
Implementation time varies greatly depending on the complexity of the pharmacy, the number of products, the customization required, and the level of integration with other systems. For a basic implementation of batch tracking and expired alert features on an existing POS system, it may take 1-3 months. However, for implementing a new POS system from scratch with all advanced features and extensive integration, a realistic timeframe could range from 3 to 6 months or even more, including the planning, development, testing, and training phases.
Q6: What is the biggest challenge in implementing and maintaining an accurate batch tracking system?
The biggest challenge often lies in initial data accuracy and operational consistency. Ensuring that every product entering or leaving is recorded with the correct batch number and expiration date requires extensive staff training and strict adherence to SOPs. Migrating old inventory data to a new system can also be a complicated process. In addition, ongoing system maintenance, including software updates, performance monitoring, and handling data anomalies, is a challenge that must be faced to maintain the accuracy and efficiency of the system in the long term.

Implementing a pharmacy POS system equipped with batch tracking and expiration alerts is no longer merely an option, but a necessity in the increasingly complex and regulated modern pharmaceutical landscape. By following this guide, you have gained an in-depth understanding of how to build a solid technical foundation, from efficient database design, implementation of runnable code, to error handling strategies and operational best practices. This solution will not only significantly improve the operational efficiency of your pharmacy, reducing losses due to expired drugs by up to tens of percent, but most importantly, it will strengthen patient safety and ensure full compliance with applicable regulations. Do not let ineffective inventory management hinder the growth and reputation of your pharmacy. If you need further assistance in designing, developing, or implementing a customized Pharmacy POS solution, integration with SIMRS or other healthcare systems, do not hesitate to contact Nugroho Setiawan. We are ready to be your technology partner to achieve smarter and safer operations.

Related solutions by PT Pilar Inovasi Informatika (Pilar Inovasi IT products): PilarPOS SIMRS (Hospital Information System) SatuSehat Integration. Need a free consultation?

Terakhir diperbarui 18 Oct 2026

Komentar

Komentar ditinjau sebelum tampil.

Belum ada komentar. Jadilah yang pertama!