medusa-plugin-quickink-logistics
v0.0.2
Published
A starter for Medusa plugins.
Downloads
19
Maintainers
Readme
medusa-plugin-quickink-logistics
Medusa v2 plugin for managing Quickink logistics serviceability by pincode, including admin CRUD, CSV bulk upsert, and admin UI tooling.
Plugin Overview
medusa-plugin-quickink-logistics adds a dedicated module for pincode-level serviceability data with:
- pincode CRUD (admin APIs)
- soft-delete aware lookups and listing scopes (
active,deleted,all) - CSV import endpoint with validation + batched upsert
- admin extension route for pincode operations
The plugin solves operational management of delivery-serviceability matrices (COD/prepaid/pickup/reverse pickup) at pincode granularity.
Medusa Version
Built for Medusa v2 (@medusajs/framework / @medusajs/medusa 2.12.4).
Installation & Setup
1) Install
npm install medusa-plugin-quickink-logisticsor
yarn add medusa-plugin-quickink-logistics2) Register plugin/module in medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
plugins: [
{
resolve: "medusa-plugin-quickink-logistics",
},
],
modules: [
{
resolve: "medusa-plugin-quickink-logistics/modules/quickink-logistics",
},
],
})3) Run migrations
npx medusa db:migrateThis is required for:
quickink_pincodetable- ID default generation migration
- partial unique active pincode index used by
ON CONFLICTCSV upsert logic
Configuration (config.ts / plugin options)
This plugin uses a runtime config object in src/config.ts for CSV import limits.
CSV Import Config Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| maxFileSizeBytes | number | No | 5 * 1024 * 1024 | Maximum upload file size in bytes for CSV import middleware and route checks. |
| maxRows | number | No | 100000 | Maximum CSV row count allowed during parsing. |
| upsertBatchSize | number | No | 2000 | Number of rows per batched INSERT ... ON CONFLICT upsert operation. |
Complete Example Config Block
// src/config.ts
export type CsvImportConfig = {
maxFileSizeBytes: number
maxRows: number
upsertBatchSize: number
}
export const csvImportConfig: CsvImportConfig = {
maxFileSizeBytes: Number(process.env.QUICKINK_CSV_MAX_FILE_SIZE_BYTES || 5 * 1024 * 1024),
maxRows: Number(process.env.QUICKINK_CSV_MAX_ROWS || 100000),
upsertBatchSize: Number(process.env.QUICKINK_CSV_UPSERT_BATCH_SIZE || 2000),
}Environment Variables
The plugin directly reads these environment variables in src/config.ts:
| Variable | Required | Default | Purpose | Example |
|---|---|---|---|---|
| QUICKINK_CSV_MAX_FILE_SIZE_BYTES | No | 5242880 | Max allowed CSV file size in bytes. | 10485760 |
| QUICKINK_CSV_MAX_ROWS | No | 100000 | Maximum parsed rows per CSV import request. | 50000 |
| QUICKINK_CSV_UPSERT_BATCH_SIZE | No | 2000 | Upsert chunk size for DB batch import. | 1000 |
REST APIs / Routes
Auth summary
adminroutes: Admin JWT/session requiredstore/plugin: Public health route
Admin Pincode APIs
Base: /admin/quickink-logistics/pincodes
GET /admin/quickink-logistics/pincodes
Lists pincodes with pagination, search, filters, sorting, and soft-delete scope.
| Query Param | Type | Required | Default | Description |
|---|---|---|---|---|
| pincode | string | No | - | Contains search (ILIKE). |
| limit | number | No | 20 | Page size (<=100). |
| offset | number | No | 0 | Offset pagination. |
| order | "created_at" \| "updated_at" \| "pincode" | No | "created_at" | Sort field. |
| order_direction | "ASC" \| "DESC" | No | "DESC" | Sort direction. |
| deleteonly | boolean | No | false | Legacy toggle; if true and list_scope omitted, scope becomes deleted. |
| list_scope | "active" \| "deleted" \| "all" | No | derived | Soft-delete scope. |
| cod_delivery | boolean | No | - | Exact boolean filter. |
| prepaid_delivery | boolean | No | - | Exact boolean filter. |
| pickup | boolean | No | - | Exact boolean filter. |
| reverse_pickup | boolean | No | - | Exact boolean filter. |
Response:
{
"pincodes": [],
"count": 0,
"offset": 0,
"limit": 20
}POST /admin/quickink-logistics/pincodes
Creates a pincode row.
| Body Field | Type | Required | Description |
|---|---|---|---|
| pincode | string | Yes | Pincode value (trimmed). |
| cod_delivery | boolean | Yes | COD support flag. |
| prepaid_delivery | boolean | Yes | Prepaid support flag. |
| pickup | boolean | Yes | Pickup support flag. |
| reverse_pickup | boolean | Yes | Reverse pickup support flag. |
Response:
{ "pincode": { "id": "qp_...", "pincode": "560001" } }Duplicate active pincode returns DUPLICATE_ERROR.
GET /admin/quickink-logistics/pincodes/:id
Returns one pincode by record ID.
PUT /admin/quickink-logistics/pincodes/:id
Partial update by record ID.
| Body Field | Type | Required |
|---|---|---|
| pincode | string | No |
| cod_delivery | boolean | No |
| prepaid_delivery | boolean | No |
| pickup | boolean | No |
| reverse_pickup | boolean | No |
At least one field is required.
DELETE /admin/quickink-logistics/pincodes/:id
Soft-deletes a pincode by record ID.
Response:
{ "id": "qp_...", "object": "quickink_pincode", "deleted": true }PUT /admin/quickink-logistics/pincodes/pincode/:pincode
Updates an active row by pincode value.
DELETE /admin/quickink-logistics/pincodes/pincode/:pincode
Soft-deletes an active row by pincode value.
CSV Import API
POST /admin/quickink-logistics/pincodes/import
Multipart upload endpoint (file field) for CSV import.
| Input | Type | Required | Description |
|---|---|---|---|
| file | CSV file | Yes | CSV upload field name must be file. |
| dry_run | boolean | No | If true, validates/parses only; no DB write. |
Dry run response:
{
"dry_run": true,
"parsed_rows": 200,
"sample": []
}Import response:
{
"dry_run": false,
"total_rows": 200,
"created": 120,
"updated": 80
}CSV parsing behavior:
- flexible/case-insensitive header matching
- accepted boolean forms:
true/false,1/0,yes/no,y/n - duplicate pincode rows in same file: last row wins
Expected canonical headers (normalized):
pincodecod_deliveryprepaid_deliverypickupreverse_pickup
Health routes
GET /admin/plugin->200GET /store/plugin->200
Important endpoint examples
curl -X GET "http://localhost:9000/admin/quickink-logistics/pincodes?limit=50&offset=0&list_scope=active&order=updated_at&order_direction=DESC" \
-H "Authorization: Bearer <admin_jwt>"curl -X POST "http://localhost:9000/admin/quickink-logistics/pincodes" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{
"pincode": "560001",
"cod_delivery": true,
"prepaid_delivery": true,
"pickup": true,
"reverse_pickup": false
}'curl -X PUT "http://localhost:9000/admin/quickink-logistics/pincodes/pincode/560001" \
-H "Authorization: Bearer <admin_jwt>" \
-H "Content-Type: application/json" \
-d '{"reverse_pickup": true}'curl -X POST "http://localhost:9000/admin/quickink-logistics/pincodes/import" \
-H "Authorization: Bearer <admin_jwt>" \
-F "[email protected]"await fetch("/admin/quickink-logistics/pincodes/import", {
method: "POST",
credentials: "include",
body: new FormData(), // append("file", file)
})Services
QuickinkLogisticsModuleService
Location: src/modules/quickink-logistics/service.ts
Manages pincode CRUD, soft-delete-aware listing, pincode-keyed updates/deletes, and CSV upsert batching.
| Method | Signature | Description |
|---|---|---|
| listPincodes | (selector?, config?) => Promise<[QuickinkPincodeDTO[], number]> | Lists pincodes with search/boolean filters and list scope. |
| getPincodeById | (id: string) => Promise<QuickinkPincodeDTO> | Retrieves one pincode by ID. |
| createPincode | (input) => Promise<QuickinkPincodeDTO> | Creates new pincode; rejects active duplicates. |
| updatePincodeByPincode | (pincode: string, input) => Promise<QuickinkPincodeDTO> | Updates by active pincode value. |
| softDeletePincodeByPincode | (pincode: string) => Promise<{ id: string }> | Soft-deletes by active pincode value. |
| updatePincode | (id: string, input) => Promise<QuickinkPincodeDTO> | Partial update by ID. |
| softDeletePincode | (id: string) => Promise<void> | Soft-delete by ID. |
| upsertFromParsedRows | (rows: ParsedCsvPincodeRow[]) => Promise<{ created: number; updated: number }> | Batched SQL upsert using partial unique conflict target on active pincode index. |
Related parser utilities in csv.ts:
parsePincodeCsv(csvText, maxRows)parseCsvBoolean(value, fieldName)
Workflows & Steps (Medusa v2)
No workflows or custom steps are implemented.src/workflows/index.ts is a placeholder export.
Subscribers / Event Hooks
No subscribers are implemented in runtime source code.
Admin UI / Widgets
Admin Route Extension: Qikink Serviceability
- Location:
src/admin/routes/qikink-serviceability/page.tsx - Placement: Admin extension route (label:
Qikink serviceability, icon:Adjustments) - Renders:
- paginated pincode table
- pincode search
- boolean filter selectors (COD/prepaid/pickup/reverse)
- list scope + sort controls
- create/edit modals
- CSV import button/input
- per-row edit/delete actions
- Interactions:
- list fetch with debounced search
- create/update/delete API calls
- CSV upload/import and toast feedback
- pagination controls
- Data consumed:
/admin/quickink-logistics/pincodes/admin/quickink-logistics/pincodes/:id/admin/quickink-logistics/pincodes/import
Models & Entities
quickink_pincode
Defined by src/modules/quickink-logistics/models/quickink-pincode.ts and migrations.
| Field | Type | Nullable | Notes |
|---|---|---|---|
| id | text | No | Primary key; DB default added in later migration. |
| pincode | text | No | Searchable pincode field. |
| cod_delivery | boolean | No | Default false. |
| prepaid_delivery | boolean | No | Default false. |
| pickup | boolean | No | Default false. |
| reverse_pickup | boolean | No | Default false. |
| created_at | timestamptz | No | Auto timestamp. |
| updated_at | timestamptz | No | Auto timestamp. |
| deleted_at | timestamptz | Yes | Soft-delete marker. |
Key index evolution (from migrations):
- initial normal
pincodeindex - later unique constraint/index attempts
- final active-only partial unique index:
IDX_quickink_pincode_pincode_active_uniqueon("pincode") WHERE "deleted_at" IS NULL
This final index is required for CSV upsert conflict target and soft-delete re-create behavior.
Use Cases & Examples
Serviceability lookup management
- Scenario: operations team maintains where logistics services are available.
- Use: admin list/create/update/delete pincode APIs.
Bulk onboarding of pincodes
- Scenario: load thousands of serviceability rows from partner exports.
- Use: CSV import endpoint with batched upsert.
Incremental pincode updates
- Scenario: quickly toggle reverse pickup for one pincode.
- Use:
PUT /admin/quickink-logistics/pincodes/pincode/:pincode.
Soft-delete and restore-friendly lifecycle
- Scenario: deprecate a pincode today and re-add later.
- Use: soft-delete APIs + active-only unique index behavior.
Ops dashboard workflow
- Scenario: non-technical admins manage records visually.
- Use: Admin extension page (
Qikink serviceability) for filtering, editing, importing.
Troubleshooting
CSV file is required
- Cause: multipart upload missing
filefield. - Fix: send
FormDatawithfile.
CSV file exceeds maximum size (...)
- Cause: upload larger than configured max.
- Fix: increase
QUICKINK_CSV_MAX_FILE_SIZE_BYTESor reduce file size.
CSV row count exceeds limit (...)
- Cause: parsed rows exceed configured max.
- Fix: increase
QUICKINK_CSV_MAX_ROWSor split CSV into smaller files.
CSV missing required columns: ...
- Cause: CSV headers missing normalized required columns.
- Fix: ensure required header set is present (case-insensitive, punctuation ignored).
Invalid boolean value for ...
- Cause: CSV boolean value not in accepted formats.
- Fix: use
true/false,1/0,yes/no, ory/n.
no unique or exclusion constraint matching the ON CONFLICT specification
- Cause: DB missing partial unique active pincode index.
- Fix: run
npx medusa db:migrateand verifyIDX_quickink_pincode_pincode_active_uniqueexists.
Pincode ... already exists
- Cause: create attempted for existing active pincode.
- Fix: update existing row or soft-delete first, then recreate.
