Return Merchandise Inspection
blocklogisticsWarehouse returns intake inspection station, RMA condition grading, and restock vs quarantine disposition workbench.
Installation
$pnpm dlx uipkge-ng@latest add return-merchandise-inspection$npx uipkge-ng@latest add return-merchandise-inspection$yarn dlx uipkge-ng@latest add return-merchandise-inspection$bunx uipkge-ng@latest add return-merchandise-inspectionManifest:
https://uipkge.dev/r/angular/return-merchandise-inspection.jsonInstalls to:src/app/components/blocks/return-merchandise-inspection/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
InspectionChecklistinterface InspectionChecklist {
tagsAttached: boolean
originalBox: boolean
allAccessories: boolean
odorStainFree: boolean
}ReturnInspectionDatainterface ReturnInspectionData {
rmaNumber: string
stationId: string
inspectorName: string
returnReason: string
item: {
name: string
variant: string
msrp: number
sku: string
upc: string
verified: boolean
}
grade: ConditionGrade
checklist: InspectionChecklist
refundAction: RefundAction
qcNotes: string
dispositionOutcome: {
route: string
destinationBin: string
lpnCode: string
priority: string
}
}GradeDefinitioninterface GradeDefinition {
id: ConditionGrade
code: string
title: string
conditionLabel: string
summary: string
route: string
targetBin: string
lpnCode: string
priority: string
badgeVariant: 'outline' | 'destructive'
badgeClasses: string
activeBorderBg: string
indicatorColor: string
statusText: string
}npm dependencies
Files installed (2)
src/app/components/blocks/return-merchandise-inspection/return-merchandise-inspection.component.ts57.1 kB
// Warehouse returns intake inspection station: RMA condition grading, restock vs // quarantine disposition routing, and customer refund action workbench with a post-submission // disposition receipt. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { AlertTriangle, ArrowRight, Barcode, Box, CheckCircle2, Clock, CornerDownLeft, CreditCard, Download, FileCheck, Gift, LucideAngularModule, Package, PackageCheck, Printer, Radio, RotateCcw, ScanLine, Tag, UserCheck, Warehouse, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiCheckboxComponent, type CheckedState } from '@/ui/checkbox/checkbox.component' import { UiRadioGroupComponent, UiRadioGroupItemComponent } from '@/ui/radio-group/radio-group.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTextareaComponent } from '@/ui/textarea/textarea.component' export type ConditionGrade = 'grade-a' | 'grade-b' | 'grade-c' | 'grade-d' export type RefundAction = 'full-refund' | 'store-credit' | 'restock-fee' export interface InspectionChecklist { tagsAttached: boolean originalBox: boolean allAccessories: boolean odorStainFree: boolean } export interface ReturnInspectionData { rmaNumber: string stationId: string inspectorName: string returnReason: string item: { name: string variant: string msrp: number sku: string upc: string verified: boolean } grade: ConditionGrade checklist: InspectionChecklist refundAction: RefundAction qcNotes: string dispositionOutcome: { route: string destinationBin: string lpnCode: string priority: string } } interface GradeDefinition { id: ConditionGrade code: string title: string conditionLabel: string summary: string route: string targetBin: string lpnCode: string priority: string badgeVariant: 'outline' | 'destructive' badgeClasses: string activeBorderBg: string indicatorColor: string statusText: string } const gradeDefinitions: Record<ConditionGrade, GradeDefinition> = { 'grade-a': { id: 'grade-a', code: 'Grade A', title: 'Grade A: Brand New / Unopened', conditionLabel: 'Brand New / Pristine', summary: 'Original tags & packaging intact', route: 'Restock to Primary Inventory', targetBin: 'Aisle 04-B · Bin #04-B-12', lpnCode: 'LPN-849201-RESTOCK-A', priority: 'High · Inventory Replenishment', badgeVariant: 'outline', badgeClasses: 'border-success/30 bg-success/10 text-success font-semibold', activeBorderBg: 'border-success/40 bg-success/5 shadow-xs ring-1 ring-success/20', indicatorColor: 'bg-success', statusText: 'Primary Inventory Restock Approved', }, 'grade-b': { id: 'grade-b', code: 'Grade B', title: 'Grade B: Open Box / Minor Cosmetic', conditionLabel: 'Open Box / Inspected', summary: 'No functional damage, packaging opened', route: 'Route to Outlet / Refurbished Store', targetBin: 'Secondary Channel · Pallet #03', lpnCode: 'LPN-849201-OUTLET-B', priority: 'Standard · Secondary Routing', badgeVariant: 'outline', badgeClasses: 'border-warning/30 bg-warning/10 text-warning font-semibold', activeBorderBg: 'border-warning/40 bg-warning/5 shadow-xs ring-1 ring-warning/20', indicatorColor: 'bg-warning', statusText: 'Secondary Channel Re-routing', }, 'grade-c': { id: 'grade-c', code: 'Grade C', title: 'Grade C: Worn / Used', conditionLabel: 'Worn / Blemished', summary: 'Minor wear on soles, visible creasing', route: 'Liquidation Batch #LQ-409', targetBin: 'Bulk Liquidation Pallet #C-88', lpnCode: 'LPN-849201-LIQUIDATE-C', priority: 'Batch Weekly Dispatch', badgeVariant: 'outline', badgeClasses: 'border-orange-500/30 bg-orange-500/10 text-orange-700 dark:text-orange-400 font-semibold', activeBorderBg: 'border-orange-500/40 bg-orange-500/5 dark:bg-orange-950/20 shadow-xs ring-1 ring-orange-500/20', indicatorColor: 'bg-orange-500', statusText: 'Wholesale Liquidation Staging', }, 'grade-d': { id: 'grade-d', code: 'Grade D', title: 'Grade D: Damaged / Defective', conditionLabel: 'Defective / Damaged', summary: 'Torn fabric, seam defect, structural flaw', route: 'Quarantine / Return to Vendor (RTV)', targetBin: 'Quarantine Bay Q-08 (Vendor Claims)', lpnCode: 'LPN-849201-QUARANTINE-D', priority: 'Urgent · Vendor RTV Credit Hold', badgeVariant: 'destructive', badgeClasses: 'border-destructive/30 bg-destructive/10 text-destructive font-semibold', activeBorderBg: 'border-destructive/40 bg-destructive/5 shadow-xs ring-1 ring-destructive/20', indicatorColor: 'bg-destructive', statusText: 'Quarantine Hold & Vendor Credit Claim', }, } const itemData = { name: 'Aero Minimalist Runner · Size 10.5 · Matte Black', variant: 'Matte Black / US 10.5 M', sku: 'AMR-BLK-105', upc: 'UPC-018249', msrp: 145.0, orderRef: '#ORD-98421', carrier: 'FedEx Return Express', trackingNumber: '7946 1102 9842', returnReason: 'Customer reported wrong size ordered', } const defaultQcNotes = 'Outer packaging opened by customer. All interior packaging materials, extra shoelaces, and brand care card verified in pristine condition. Zero sole wear detected.' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-return-merchandise-inspection, [ui-return-merchandise-inspection]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiCheckboxComponent, UiRadioGroupComponent, UiRadioGroupItemComponent, UiSeparatorComponent, UiTextareaComponent, ], template: ` <div data-slot="return-merchandise-inspection" [class]="rootClass"> @if (submitted()) { <!-- POST-SUBMISSION CONFIRMATION RECEIPT VIEW --> <div class="space-y-6"> <div ui-card class="border-border overflow-hidden shadow-xs"> <div [class]="receiptBannerClass"> <div class="relative mx-auto mb-4 size-16"> <span [class]="receiptGlowClass" aria-hidden="true"></span> <span [class]="receiptIconWrapClass"> @if (selectedGrade() !== 'grade-d') { <lucide-icon [img]="PackageCheck" class="size-8" /> } @else { <lucide-icon [img]="AlertTriangle" class="size-8" /> } </span> </div> <span ui-badge [variant]="selectedGrade() === 'grade-d' ? 'destructive' : 'outline'" [class]="receiptBadgeClass" > RMA Disposition Manifest Dispatched </span> <h2 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl"> {{ selectedGrade() === 'grade-d' ? 'RMA Inspection Flagged · Quarantine RTV Initiated' : 'Inspection Complete · Automated Routing Logged' }} </h2> <p class="text-muted-foreground mx-auto mt-1.5 max-w-2xl text-xs sm:text-sm"> Disposition for <span class="text-foreground font-medium">{{ item.name }}</span> has been recorded. Warehouse routing slip and automated customer refund authorization have been queued. </p> <div class="border-border bg-muted/60 mt-4 inline-flex flex-wrap items-center gap-2 rounded-full border px-3.5 py-1 text-xs" > <span class="text-muted-foreground font-medium">RMA Reference:</span> <span class="text-foreground font-mono font-bold">{{ rmaNumber() }}</span> <span class="text-muted-foreground font-medium">LPN:</span> <span class="text-foreground font-mono font-bold">{{ currentGrade().lpnCode }}</span> </div> </div> <div ui-card-content class="space-y-6 p-6"> <!-- Disposition Summary Meta Grid --> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5"> <span class="text-muted-foreground block text-xs font-medium">Condition Grade</span> <span class="text-foreground block text-sm font-semibold">{{ currentGrade().title }}</span> <span class="text-muted-foreground text-xs">{{ currentGrade().summary }}</span> </div> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5"> <span class="text-muted-foreground block text-xs font-medium">Warehouse Destination</span> <span class="text-foreground block text-sm font-semibold">{{ currentGrade().targetBin }}</span> <span class="text-muted-foreground text-xs">{{ currentGrade().route }}</span> </div> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5"> <span class="text-muted-foreground block text-xs font-medium">Customer Resolution</span> <span class="text-foreground block text-sm font-semibold tabular-nums"> {{ formatCurrency(refundCalculations().totalRefund) }} </span> <span class="text-muted-foreground block truncate text-xs">{{ refundCalculations().methodLabel }}</span> </div> <div class="border-border bg-muted/20 space-y-1 rounded-lg border p-3.5"> <span class="text-muted-foreground block text-xs font-medium">QC Inspector</span> <span class="text-foreground block text-sm font-semibold">{{ inspectorName }}</span> <span class="text-muted-foreground text-xs">Station #02 · Timestamped</span> </div> </div> <!-- LPN Barcode & Routing Pass Box --> <div class="border-border bg-muted/15 space-y-3 rounded-xl border p-4 sm:p-5"> <div class="flex flex-wrap items-center justify-between gap-2"> <div class="flex flex-wrap items-center gap-2"> <lucide-icon [img]="Barcode" class="text-primary size-4" /> <span class="text-foreground text-xs font-semibold tracking-wide uppercase"> Pallet / Tote License Plate Number (LPN) </span> </div> <span ui-badge variant="outline" class="font-mono text-xs">READY FOR PUTAWAY</span> </div> <div class="border-border bg-card flex flex-col items-center justify-center space-y-2 rounded-lg border p-4 text-center" > <!-- Barcode SVG visualization --> <svg class="text-foreground h-12 w-full max-w-sm" viewBox="0 0 280 48" fill="currentColor" aria-label="LPN Warehouse Barcode" > <rect x="0" y="0" width="4" height="48" /> <rect x="7" y="0" width="2" height="48" /> <rect x="12" y="0" width="5" height="48" /> <rect x="20" y="0" width="2" height="48" /> <rect x="25" y="0" width="6" height="48" /> <rect x="34" y="0" width="3" height="48" /> <rect x="40" y="0" width="2" height="48" /> <rect x="45" y="0" width="5" height="48" /> <rect x="53" y="0" width="3" height="48" /> <rect x="60" y="0" width="6" height="48" /> <rect x="69" y="0" width="2" height="48" /> <rect x="74" y="0" width="4" height="48" /> <rect x="81" y="0" width="3" height="48" /> <rect x="88" y="0" width="5" height="48" /> <rect x="96" y="0" width="2" height="48" /> <rect x="101" y="0" width="6" height="48" /> <rect x="110" y="0" width="3" height="48" /> <rect x="116" y="0" width="4" height="48" /> <rect x="123" y="0" width="2" height="48" /> <rect x="128" y="0" width="5" height="48" /> <rect x="136" y="0" width="3" height="48" /> <rect x="142" y="0" width="6" height="48" /> <rect x="151" y="0" width="2" height="48" /> <rect x="156" y="0" width="4" height="48" /> <rect x="163" y="0" width="3" height="48" /> <rect x="170" y="0" width="5" height="48" /> <rect x="178" y="0" width="2" height="48" /> <rect x="183" y="0" width="6" height="48" /> <rect x="192" y="0" width="4" height="48" /> <rect x="199" y="0" width="2" height="48" /> <rect x="204" y="0" width="5" height="48" /> <rect x="212" y="0" width="3" height="48" /> <rect x="218" y="0" width="6" height="48" /> <rect x="227" y="0" width="2" height="48" /> <rect x="232" y="0" width="4" height="48" /> <rect x="239" y="0" width="3" height="48" /> <rect x="245" y="0" width="5" height="48" /> <rect x="253" y="0" width="2" height="48" /> <rect x="258" y="0" width="6" height="48" /> <rect x="267" y="0" width="3" height="48" /> <rect x="273" y="0" width="4" height="48" /> </svg> <div class="flex items-center gap-3 font-mono text-xs font-bold"> <span class="text-foreground tracking-wider">{{ currentGrade().lpnCode }}</span> <span class="text-muted-foreground font-normal">{{ currentGrade().targetBin }}</span> </div> </div> <!-- Inspector Notes Display --> @if (qcNotes()) { <div class="space-y-1 pt-1 text-xs"> <span class="text-muted-foreground font-medium">Logged Inspector Notes:</span> <p class="border-border bg-card text-foreground rounded-md border p-3 leading-relaxed italic"> “{{ qcNotes() }}” </p> </div> } </div> <!-- Actions Strip --> <div class="flex flex-wrap items-center justify-between gap-3 pt-2"> <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs font-medium" (click)="handleReset()" > <lucide-icon [img]="RotateCcw" class="size-3.5" /> Start Next RMA Inspection </button> <div class="flex flex-wrap items-center gap-2"> <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs font-medium"> <lucide-icon [img]="Printer" class="size-3.5" /> Print LPN Tote Label </button> <button ui-button aria-label="Download attachment" size="sm" class="gap-1.5 text-xs font-medium"> <lucide-icon [img]="Download" class="size-3.5" /> Download Disposition PDF </button> </div> </div> </div> </div> </div> } @else { <!-- ACTIVE INSPECTION WORKBENCH VIEW --> <div class="space-y-6"> <!-- Top Station Header Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> <div class="space-y-2"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge variant="outline" class="border-primary/30 bg-primary/10 text-primary gap-1.5 py-0.5 text-xs" > <lucide-icon [img]="Warehouse" class="size-3.5" /> Station #02 · Returns Intake & Grading </span> <span ui-badge variant="outline" class="gap-1.5 py-0.5 font-mono text-xs"> <lucide-icon [img]="Tag" class="size-3" /> {{ rmaNumber() }} </span> <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1.5 py-0.5 text-xs" > <lucide-icon [img]="Radio" class="text-success size-3" /> Scanner Online </span> </div> <div> <h3 ui-card-title class="text-xl font-bold tracking-tight sm:text-2xl"> Warehouse Returns Intake & Grading Station #02 </h3> <p ui-card-description class="text-xs sm:text-sm"> RMA intake verification, physical condition evaluation, restock/quarantine disposition routing, and customer refund action. </p> </div> </div> <!-- Primary Header CTA --> <div class="flex shrink-0 items-center gap-2"> <button ui-button type="button" size="default" class="gap-2 text-xs font-semibold sm:text-sm" (click)="handleCompleteInspection()" > <lucide-icon [img]="FileCheck" class="size-4" /> Complete Inspection & Disposition </button> </div> </div> </div> <!-- Metadata Ribbon Bar --> <div ui-card-content class="pt-0 pb-4"> <div class="border-border bg-muted/20 grid gap-3 rounded-lg border p-3.5 sm:grid-cols-2 lg:grid-cols-4"> <div class="space-y-0.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Tag" class="text-primary size-3.5" /> <span>RMA Reference</span> </div> <p class="text-foreground font-mono text-xs font-semibold">{{ rmaNumber() }}</p> <p class="text-muted-foreground text-xs">Order: {{ item.orderRef }}</p> </div> <div class="space-y-0.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="UserCheck" class="text-primary size-3.5" /> <span>Inspector / Station</span> </div> <p class="text-foreground text-xs font-semibold">{{ inspectorName }}</p> <p class="text-muted-foreground text-xs">Terminal Station #02</p> </div> <div class="space-y-0.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="CornerDownLeft" class="text-primary size-3.5" /> <span>Customer Return Reason</span> </div> <p class="text-foreground truncate text-xs font-semibold">{{ item.returnReason }}</p> <p class="text-muted-foreground text-xs">Carrier: {{ item.carrier }}</p> </div> <div class="space-y-0.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Clock" class="text-primary size-3.5" /> <span>Intake Status</span> </div> <p class="text-foreground text-xs font-semibold">In Progress · Pending Disposition</p> <p class="text-muted-foreground font-mono text-xs">{{ item.trackingNumber }}</p> </div> </div> </div> </div> <!-- 2-COLUMN INSPECTION WORKBENCH --> <div class="grid grid-cols-1 items-start gap-6 lg:grid-cols-12"> <!-- LEFT COLUMN: Item Verification & Condition Grading (7 Cols) --> <div class="space-y-6 lg:col-span-7"> <!-- 1. Scanned Item Verification Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="flex flex-wrap items-center gap-2"> <lucide-icon [img]="ScanLine" class="text-primary size-4" /> <h3 ui-card-title class="text-sm font-semibold">Intake Item Scanned</h3> </div> <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1 text-xs"> <lucide-icon [img]="CheckCircle2" class="size-3" /> Match Verified </span> </div> </div> <div ui-card-content class="space-y-4"> <div class="border-border bg-card flex flex-col gap-3 rounded-lg border p-3.5 sm:flex-row sm:items-center" > <div class="border-border bg-muted/60 text-muted-foreground flex size-14 shrink-0 items-center justify-center rounded-lg border" > <lucide-icon [img]="Package" class="text-primary size-7" /> </div> <div class="min-w-0 flex-1 space-y-1"> <div class="flex flex-wrap items-start justify-between gap-2"> <div> <h4 class="text-foreground text-sm leading-tight font-semibold"> {{ item.name }} </h4> <p class="text-muted-foreground text-xs"> {{ item.variant }} · SKU: <span class="font-mono font-medium">{{ item.sku }}</span> </p> </div> <div class="text-right"> <span class="text-foreground text-base font-bold tabular-nums"> {{ formatCurrency(item.msrp) }} </span> <span class="text-muted-foreground block text-xs">MSRP</span> </div> </div> <div class="flex flex-wrap items-center gap-3 pt-1 text-xs"> <div class="text-muted-foreground flex items-center gap-1.5 font-mono"> <lucide-icon [img]="Barcode" class="text-success size-3.5" /> <span class="text-foreground font-semibold">{{ item.upc }}</span> <span ui-badge variant="outline" class="text-success h-4 px-1.5 py-0 text-xs">UPC OK</span> </div> <ui-separator orientation="vertical" class="h-3.5" /> <span class="text-muted-foreground">Order: {{ item.orderRef }}</span> </div> </div> </div> </div> </div> <!-- 2. Condition Grading Radio Cards --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="space-y-0.5"> <h3 ui-card-title class="text-sm font-semibold">Condition Grading Assessment</h3> <p ui-card-description class="text-xs"> Select physical grading level to trigger automated warehouse routing rules. </p> </div> <span ui-badge [class]="currentGrade().badgeClasses"> {{ currentGrade().code }} </span> </div> </div> <div ui-card-content> <ui-radio-group [value]="selectedGrade()" (valueChange)="setGrade($event)" class="grid grid-cols-1 gap-2.5" > <!-- Grade A --> <div [class]="gradeCardClass('grade-a')" (click)="selectedGrade.set('grade-a')"> <ui-radio-group-item id="grade-a" value="grade-a" class="mt-0.5" /> <div class="flex-1 space-y-1"> <div class="flex flex-wrap items-center justify-between gap-2"> <label for="grade-a" class="text-foreground cursor-pointer text-xs font-semibold sm:text-sm"> Grade A: Brand New / Unopened </label> <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success text-xs"> Primary Restock </span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Original factory tags & packaging intact. No cosmetic or functional flaws. </p> <div class="text-muted-foreground flex items-center gap-1.5 pt-1 text-xs"> <lucide-icon [img]="ArrowRight" class="text-primary size-3" /> <span >Target: <strong class="text-foreground font-medium" >Restock to Primary Inventory (Aisle 04-B)</strong ></span > </div> </div> </div> <!-- Grade B --> <div [class]="gradeCardClass('grade-b')" (click)="selectedGrade.set('grade-b')"> <ui-radio-group-item id="grade-b" value="grade-b" class="mt-0.5" /> <div class="flex-1 space-y-1"> <div class="flex flex-wrap items-center justify-between gap-2"> <label for="grade-b" class="text-foreground cursor-pointer text-xs font-semibold sm:text-sm"> Grade B: Open Box / Minor Cosmetic </label> <span ui-badge variant="outline" class="border-warning/30 bg-warning/10 text-warning text-xs"> Outlet Store </span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Box opened or minor package scuffs, but product is 100% unworn and fully functional. </p> <div class="text-muted-foreground flex items-center gap-1.5 pt-1 text-xs"> <lucide-icon [img]="ArrowRight" class="text-primary size-3" /> <span >Target: <strong class="text-foreground font-medium" >Route to Outlet / Refurbished Store</strong ></span > </div> </div> </div> <!-- Grade C --> <div [class]="gradeCardClass('grade-c')" (click)="selectedGrade.set('grade-c')"> <ui-radio-group-item id="grade-c" value="grade-c" class="mt-0.5" /> <div class="flex-1 space-y-1"> <div class="flex flex-wrap items-center justify-between gap-2"> <label for="grade-c" class="text-foreground cursor-pointer text-xs font-semibold sm:text-sm"> Grade C: Worn / Used </label> <span ui-badge variant="outline" class="border-orange-500/30 bg-orange-500/10 text-xs text-orange-700 dark:text-orange-400" > Liquidation </span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Visible wear on soles, minor creasing or cosmetic stains. Not suitable for retail shelves. </p> <div class="text-muted-foreground flex items-center gap-1.5 pt-1 text-xs"> <lucide-icon [img]="ArrowRight" class="text-primary size-3" /> <span >Target: <strong class="text-foreground font-medium" >Liquidation Batch #LQ-409 (Wholesale Lot)</strong ></span > </div> </div> </div> <!-- Grade D --> <div [class]="gradeCardClass('grade-d')" (click)="selectedGrade.set('grade-d')"> <ui-radio-group-item id="grade-d" value="grade-d" class="mt-0.5" /> <div class="flex-1 space-y-1"> <div class="flex flex-wrap items-center justify-between gap-2"> <label for="grade-d" class="text-foreground cursor-pointer text-xs font-semibold sm:text-sm"> Grade D: Damaged / Defective </label> <span ui-badge variant="destructive" class="text-xs">Quarantine RTV</span> </div> <p class="text-muted-foreground text-xs leading-relaxed"> Torn fabric, broken seam, outsole separation, or manufacturing defect. </p> <div class="text-muted-foreground flex items-center gap-1.5 pt-1 text-xs"> <lucide-icon [img]="ArrowRight" class="text-primary size-3" /> <span >Target: <strong class="text-foreground font-medium" >Quarantine Bay Q-08 / Return to Vendor (RTV)</strong ></span > </div> </div> </div> </ui-radio-group> </div> </div> <!-- 3. Physical Inspection Checklist --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="space-y-0.5"> <h3 ui-card-title class="text-sm font-semibold">Physical Inspection Checklist</h3> <p ui-card-description class="text-xs"> Verify all physical criteria before approving final warehouse putaway. </p> </div> <div class="flex flex-wrap items-center gap-2"> <span ui-badge variant="secondary" class="font-mono text-xs"> {{ checkedChecklistCount() }}/{{ totalChecklistCount }} Passed </span> <button ui-button type="button" variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground h-7 text-xs" (click)="selectAllChecklist(!allChecksPassed())" > {{ allChecksPassed() ? 'Uncheck All' : 'Pass All' }} </button> </div> </div> </div> <div ui-card-content class="space-y-2.5"> <!-- Item 1: Tags Attached --> <div [class]="checkRowClass(checklist().tagsAttached)"> <ui-checkbox id="chk-tags" [checked]="checklist().tagsAttached" (checkedChange)="setCheck('tagsAttached', $event)" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <label for="chk-tags" class="text-foreground cursor-pointer text-xs font-semibold select-none"> Tags attached </label> <p class="text-muted-foreground text-xs"> Factory hangtags, barcode UPC stickers, and care labels intact. </p> </div> </div> <!-- Item 2: Original Box Present --> <div [class]="checkRowClass(checklist().originalBox)"> <ui-checkbox id="chk-box" [checked]="checklist().originalBox" (checkedChange)="setCheck('originalBox', $event)" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <label for="chk-box" class="text-foreground cursor-pointer text-xs font-semibold select-none"> Original box present </label> <p class="text-muted-foreground text-xs"> Manufacturer shoebox present with readable SKU and size label. </p> </div> </div> <!-- Item 3: Accessories in Box --> <div [class]="checkRowClass(checklist().allAccessories)"> <ui-checkbox id="chk-acc" [checked]="checklist().allAccessories" (checkedChange)="setCheck('allAccessories', $event)" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <label for="chk-acc" class="text-foreground cursor-pointer text-xs font-semibold select-none"> All accessories in box </label> <p class="text-muted-foreground text-xs"> Extra pair of laces, dust bag, and product manual verified. </p> </div> </div> <!-- Item 4: Odor and Stain Free --> <div [class]="checkRowClass(checklist().odorStainFree)"> <ui-checkbox id="chk-odor" [checked]="checklist().odorStainFree" (checkedChange)="setCheck('odorStainFree', $event)" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <label for="chk-odor" class="text-foreground cursor-pointer text-xs font-semibold select-none"> Odor & stain free </label> <p class="text-muted-foreground text-xs"> Free from perfume scent, tobacco smoke, pet hair, dirt, or cosmetic scuffs. </p> </div> </div> </div> </div> </div> <!-- RIGHT COLUMN: Disposition & Customer Refund Action Card (5 Cols) --> <div class="space-y-6 lg:col-span-5"> <!-- 1. Selected Automated Warehouse Disposition Card --> <div ui-card class="border-border overflow-hidden shadow-xs"> <div ui-card-header class="bg-muted/15 border-border border-b pb-3"> <div class="flex items-center justify-between"> <div class="flex flex-wrap items-center gap-2"> <lucide-icon [img]="Warehouse" class="text-primary size-4" /> <h3 ui-card-title class="text-sm font-semibold">Warehouse Routing Outcome</h3> </div> <span ui-badge [class]="currentGrade().badgeClasses"> {{ currentGrade().code }} </span> </div> </div> <div ui-card-content class="space-y-4 p-4"> <!-- Active Outcome Card Banner --> <div [class]="outcomeBannerClass"> <div class="flex items-start gap-3"> <div class="border-border bg-background/80 text-foreground flex size-9 shrink-0 items-center justify-center rounded-md border" > <lucide-icon [img]="Box" class="text-primary size-5" /> </div> <div class="min-w-0 flex-1 space-y-1"> <div class="flex flex-wrap items-center gap-2"> <span class="text-foreground text-xs font-bold sm:text-sm"> {{ currentGrade().route }} </span> </div> <p class="text-muted-foreground text-xs"> Destination Bin: <strong class="text-foreground font-mono font-semibold">{{ currentGrade().targetBin }}</strong> </p> <p class="text-muted-foreground text-xs"> Priority: <span class="text-foreground font-medium">{{ currentGrade().priority }}</span> </p> </div> </div> <div class="border-border/60 mt-3.5 flex items-center justify-between border-t pt-3 text-xs"> <span class="text-muted-foreground font-mono">LPN: {{ currentGrade().lpnCode }}</span> <span ui-badge variant="outline" class="font-mono text-xs">READY FOR SCAN</span> </div> </div> </div> </div> <!-- 2. Customer Refund Action Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="space-y-0.5"> <h3 ui-card-title class="text-sm font-semibold">Customer Refund Action</h3> <p ui-card-description class="text-xs">Choose financial settlement for RMA {{ rmaNumber() }}.</p> </div> <lucide-icon [img]="CreditCard" class="text-muted-foreground size-4" /> </div> </div> <div ui-card-content class="space-y-4"> <ui-radio-group [value]="selectedRefundAction()" (valueChange)="setRefundAction($event)" class="grid grid-cols-1 gap-2.5" > <!-- Option 1: Full Refund --> <div [class]="refundCardClass('full-refund')" (click)="selectedRefundAction.set('full-refund')"> <ui-radio-group-item id="ref-full" value="full-refund" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <div class="flex items-center justify-between"> <label for="ref-full" class="text-foreground cursor-pointer text-xs font-semibold"> Authorize Full Refund </label> <span class="text-foreground text-xs font-semibold tabular-nums"> {{ formatCurrency(item.msrp) }} </span> </div> <p class="text-muted-foreground text-xs"> 100% refund to <span class="text-foreground font-medium">Visa •••• 4242</span>. 3–5 business days. </p> </div> </div> <!-- Option 2: Store Credit (+10% Bonus) --> <div [class]="refundCardClass('store-credit')" (click)="selectedRefundAction.set('store-credit')"> <ui-radio-group-item id="ref-credit" value="store-credit" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <div class="flex items-center justify-between"> <div class="flex items-center gap-1.5"> <lucide-icon [img]="Gift" class="text-success size-3" /> <label for="ref-credit" class="text-foreground cursor-pointer text-xs font-semibold"> Issue Store Credit (+10%) </label> </div> <span class="text-success text-success text-xs font-semibold tabular-nums"> {{ formatCurrency(item.msrp * 1.1) }} </span> </div> <p class="text-muted-foreground text-xs"> Includes <span class="text-foreground font-medium">+{{ formatCurrency(item.msrp * 0.1) }}</span> bonus credit. Delivered instantly by email. </p> </div> </div> <!-- Option 3: Charge Restocking Fee --> <div [class]="refundCardClass('restock-fee')" (click)="selectedRefundAction.set('restock-fee')"> <ui-radio-group-item id="ref-fee" value="restock-fee" class="mt-0.5" /> <div class="flex-1 space-y-0.5"> <div class="flex items-center justify-between"> <label for="ref-fee" class="text-foreground cursor-pointer text-xs font-semibold"> Charge Restocking Fee (-$15) </label> <span class="text-foreground text-xs font-semibold tabular-nums"> {{ formatCurrency(item.msrp - 15) }} </span> </div> <p class="text-muted-foreground text-xs"> $15.00 repackaging & inspection deduction. Net to <span class="text-foreground font-medium">Visa •••• 4242</span>. </p> </div> </div> </ui-radio-group> <!-- Settlement Calculation Summary Box --> <div class="border-border bg-muted/30 space-y-1.5 rounded-lg border p-3 text-xs"> <div class="text-muted-foreground flex items-center justify-between"> <span>Item MSRP</span> <span class="text-foreground font-mono font-medium">{{ formatCurrency(refundCalculations().subtotal) }}</span> </div> @if (selectedRefundAction() === 'store-credit') { <div class="text-success flex items-center justify-between"> <span>{{ refundCalculations().adjustmentLabel }}</span> <span class="font-mono font-medium" >+{{ formatCurrency(refundCalculations().adjustmentAmount) }}</span > </div> } @else if (selectedRefundAction() === 'restock-fee') { <div class="text-destructive flex items-center justify-between"> <span>{{ refundCalculations().adjustmentLabel }}</span> <span class="font-mono font-medium" >-{{ formatCurrency(refundCalculations().adjustmentAmount) }}</span > </div> } <div class="border-border text-foreground flex items-center justify-between border-t pt-1.5 font-bold" > <span>Authorized Net Settlement</span> <span class="font-mono text-sm font-bold tabular-nums" [class.text-success]="selectedRefundAction() === 'store-credit'" > {{ formatCurrency(refundCalculations().totalRefund) }} </span> </div> </div> </div> </div> <!-- 3. Internal QC Inspection Notes --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="space-y-0.5"> <h3 ui-card-title class="text-sm font-semibold">Internal QC Inspection Notes</h3> <p ui-card-description class="text-xs">Station audit log appended to inventory history.</p> </div> </div> </div> <div ui-card-content class="space-y-3"> <ui-textarea [value]="qcNotes()" (valueChange)="qcNotes.set($event)" [rows]="3" placeholder="Enter internal inspection notes, physical defect description, or warehouse packaging details..." class="text-xs" /> <!-- Quick Tag Helpers --> <div class="space-y-1.5"> <span class="text-muted-foreground block text-xs font-medium">Quick Preset Tags:</span> <div class="flex flex-wrap gap-1.5"> <button type="button" class="border-border bg-muted/40 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 cursor-pointer rounded-md border px-2 py-0.5 text-xs transition-colors" (click)="applyPresetNote('Pristine unworn condition.')" > + Pristine Condition </button> <button type="button" class="border-border bg-muted/40 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 cursor-pointer rounded-md border px-2 py-0.5 text-xs transition-colors" (click)="applyPresetNote('Minor outer box scuff; shoes mint.')" > + Box Scuffed </button> <button type="button" class="border-border bg-muted/40 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 cursor-pointer rounded-md border px-2 py-0.5 text-xs transition-colors" (click)="applyPresetNote('Slight sole friction detected.')" > + Sole Friction </button> <button type="button" class="border-border bg-muted/40 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 cursor-pointer rounded-md border px-2 py-0.5 text-xs transition-colors" (click)="applyPresetNote('Factory seam defect identified - Flagged RTV.')" > + Flagged RTV </button> </div> </div> </div> <div ui-card-footer class="border-border bg-muted/15 flex items-center justify-between border-t p-4"> <span class="text-muted-foreground text-xs"> Audit sign-off: <strong class="text-foreground font-medium">{{ inspectorName }}</strong> </span> <button ui-button size="sm" class="gap-1.5 text-xs font-semibold" (click)="handleCompleteInspection()" > <lucide-icon [img]="FileCheck" class="size-3.5" /> Complete Inspection </button> </div> </div> </div> </div> </div> } </div> `, }) export class UiReturnMerchandiseInspectionComponent { protected readonly AlertTriangle = AlertTriangle protected readonly ArrowRight = ArrowRight protected readonly Barcode = Barcode protected readonly Box = Box protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly CornerDownLeft = CornerDownLeft protected readonly CreditCard = CreditCard protected readonly Download = Download protected readonly FileCheck = FileCheck protected readonly Gift = Gift protected readonly Package = Package protected readonly PackageCheck = PackageCheck protected readonly Printer = Printer protected readonly Radio = Radio protected readonly RotateCcw = RotateCcw protected readonly ScanLine = ScanLine protected readonly Tag = Tag protected readonly UserCheck = UserCheck protected readonly Warehouse = Warehouse protected readonly item = itemData protected readonly totalChecklistCount = 4 @Input() set initialRma(v: string | undefined) { if (v !== undefined) this.rmaNumber.set(v) } @Input() set initialGrade(v: ConditionGrade | undefined) { if (v !== undefined) this.selectedGrade.set(v) } @Input() set initialRefundAction(v: RefundAction | undefined) { if (v !== undefined) this.selectedRefundAction.set(v) } @Input() set initialSubmitted(v: boolean | undefined) { if (v !== undefined) this.submitted.set(!!v) } @Input() inspectorName = 'Alex Rivera · QC Lead' @Input() stationName = 'Warehouse Returns Intake & Grading Station #02' @Input('class') className?: string @Output() complete = new EventEmitter<ReturnInspectionData>() readonly rmaNumber = signal('#RMA-849201') readonly selectedGrade = signal<ConditionGrade>('grade-a') readonly selectedRefundAction = signal<RefundAction>('full-refund') readonly submitted = signal(false) readonly qcNotes = signal(defaultQcNotes) readonly checklist = signal<InspectionChecklist>({ tagsAttached: true, originalBox: true, allAccessories: true, odorStainFree: true, }) readonly currentGrade = computed(() => gradeDefinitions[this.selectedGrade()]) readonly checkedChecklistCount = computed(() => { const c = this.checklist() return [c.tagsAttached, c.originalBox, c.allAccessories, c.odorStainFree].filter(Boolean).length }) readonly allChecksPassed = computed(() => this.checkedChecklistCount() === this.totalChecklistCount) readonly refundCalculations = computed(() => { const msrp = itemData.msrp if (this.selectedRefundAction() === 'store-credit') { const bonus = msrp * 0.1 return { subtotal: msrp, adjustmentLabel: 'Store Credit Bonus (+10%)', adjustmentAmount: bonus, isBonus: true, totalRefund: msrp + bonus, methodLabel: 'Digital Gift Card ([email protected])', payoutSpeed: 'Instant email delivery upon grading', } } if (this.selectedRefundAction() === 'restock-fee') { const fee = 15.0 return { subtotal: msrp, adjustmentLabel: 'Restocking & Repacking Fee', adjustmentAmount: fee, isBonus: false, totalRefund: msrp - fee, methodLabel: 'Net Credit to Visa •••• 4242', payoutSpeed: '3–5 business days to card', } } return { subtotal: msrp, adjustmentLabel: 'Restocking Fee Waived', adjustmentAmount: 0, isBonus: false, totalRefund: msrp, methodLabel: 'Full Refund to Visa •••• 4242', payoutSpeed: '3–5 business days to card', } }) get rootClass(): string { return cn('text-foreground mx-auto w-full max-w-6xl space-y-6', this.className) } get receiptBannerClass(): string { return cn( 'border-b p-6 text-center sm:p-8', this.selectedGrade() === 'grade-d' ? 'border-destructive/30 bg-destructive/10' : 'border-success/30 bg-success/10', ) } get receiptGlowClass(): string { return cn( 'absolute inset-0 rounded-full blur-xl', this.selectedGrade() === 'grade-d' ? 'bg-destructive/30' : 'bg-success/30', ) } get receiptIconWrapClass(): string { return cn( 'relative flex size-16 items-center justify-center rounded-full border shadow-xs', this.selectedGrade() === 'grade-d' ? 'border-destructive/40 bg-destructive/20 text-destructive' : 'border-success/40 bg-success/20 text-success', ) } get receiptBadgeClass(): string { return cn( 'mb-2 font-mono text-xs tracking-wider uppercase', this.selectedGrade() !== 'grade-d' && 'border-success/30 bg-success/10 text-success', ) } get outcomeBannerClass(): string { return cn('rounded-lg border p-4 transition-colors', this.currentGrade().activeBorderBg) } gradeCardClass(grade: ConditionGrade): string { return cn( 'flex cursor-pointer items-start gap-3 rounded-lg border p-3.5 transition-colors select-none', this.selectedGrade() === grade ? gradeDefinitions[grade].activeBorderBg : 'border-border bg-card hover:bg-muted/30', ) } refundCardClass(action: RefundAction): string { const active = action === 'store-credit' ? 'border-success/50 bg-success/5 ring-success/20 dark:bg-success/10 shadow-xs ring-1' : 'border-primary bg-primary/[0.03] dark:bg-primary/10 shadow-xs' return cn( 'flex cursor-pointer items-start gap-3 rounded-lg border p-3 transition-colors select-none', this.selectedRefundAction() === action ? active : 'border-border bg-card hover:bg-muted/30', ) } checkRowClass(passed: boolean): string { return cn( 'flex items-start gap-3 rounded-lg border p-3 transition-colors', passed ? 'border-border bg-card' : 'border-warning/30 bg-warning/5', ) } setGrade(value: string): void { this.selectedGrade.set(value as ConditionGrade) } setRefundAction(value: string): void { this.selectedRefundAction.set(value as RefundAction) } setCheck(key: keyof InspectionChecklist, value: CheckedState): void { const checked = value === true this.checklist.update((prev) => ({ ...prev, [key]: checked })) } formatCurrency(val: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, }).format(val) } selectAllChecklist(pass: boolean): void { this.checklist.set({ tagsAttached: pass, originalBox: pass, allAccessories: pass, odorStainFree: pass }) } applyPresetNote(note: string): void { const current = this.qcNotes() if (!current.includes(note)) this.qcNotes.set(current ? `${current} ${note}` : note) } handleCompleteInspection(): void { this.submitted.set(true) const grade = this.currentGrade() this.complete.emit({ rmaNumber: this.rmaNumber(), stationId: 'STATION-02-B', inspectorName: this.inspectorName, returnReason: itemData.returnReason, item: { name: itemData.name, variant: itemData.variant, msrp: itemData.msrp, sku: itemData.sku, upc: itemData.upc, verified: true, }, grade: this.selectedGrade(), checklist: { ...this.checklist() }, refundAction: this.selectedRefundAction(), qcNotes: this.qcNotes(), dispositionOutcome: { route: grade.route, destinationBin: grade.targetBin, lpnCode: grade.lpnCode, priority: grade.priority, }, }) } handleReset(): void { this.submitted.set(false) this.selectedGrade.set('grade-a') this.selectedRefundAction.set('full-refund') this.selectAllChecklist(true) this.qcNotes.set(defaultQcNotes) } }src/app/components/blocks/return-merchandise-inspection/index.ts0.2 kB
export { UiReturnMerchandiseInspectionComponent, type ConditionGrade, type RefundAction, type InspectionChecklist, type ReturnInspectionData, } from './return-merchandise-inspection.component'
Raw manifest:https://uipkge.dev/r/angular/return-merchandise-inspection.json