Jump to content
thirty bees forum

Recommended Posts

Posted

Hi there.

I would like to start a discussion on the RMA part of thirty bees.

How do you use it? Do you find it easy to use? Do your customers find it easy to navigate? What modules do you have that are using it?

What would you improve in it? Do you use it only for warranties/claims/etc. or also for product exchanges, etc? What is your typical use case/flow?

------------
I'm currently not using it as I find it very limited and hard to use for me and for my customers. I handle all (very few) quality/warranty claims via email/phone and I want to start using the RMA for this.

BUT, with the slow growth of my shops I started experiencing a rise in exchange requests for items that are the wrong size. The customer wishes to return the initially purchased item and receive a different size. For now, they have to call/email me to arrange this. I would like to centralize this flow in one place. Preferably with all other returns, different from the cool-off period returns (which are almost non-existent for me for now).

I have the idea to use the current RMA for this. Here is what the LLM spit out as an idea, and I quite like it.

I would like to see the RMA as a one-stop-shop for all warranty and exchange flows in the shop. In the future, we can even implement some connection to the Withdrawal module so we can truly have everything in one place.

I would like for the customer to have the opportunity to file a claim in their profile, so we can handle every request in a specific flow.

I would call this an evolution and a step forward for the RMA system that will truly make it useful for me (and hopefully others).

Will you use something similar if you currently don't use the RMA as well, and what ideas do you have that you think will be useful in your work day?
 

Quote

The current thirty bees RMA model is quite minimal: OrderReturn stores the order, customer, one numeric state, a free-text question and dates; order_return_detail essentially identifies the original order line/customization and quantity. Return states are also basically just configurable names/colors with an active flag. That is a good base for backwards compatibility, but not enough for a modern after-sales workflow.

The model I would aim for

Think of an RMA as an after-sales case:

Order → RMA case → RMA items → desired resolution → inbound shipment → inspection → resolution → outbound/refund

For each returned item, the customer should choose two separate things:

Concept Examples
Reason Too small, too large, wrong item, defective, damaged, changed mind
Requested resolution Exchange, refund, repair, store credit
Exchange target Same product → size XL
Quantity 1
Comment Optional
Photos Optional/required depending on reason

So:

“Size L is too small; exchange it for XL”

becomes structured data instead of something written into question.

That distinction will make the system dramatically more useful later.


Backwards compatibility

I would not replace order_return or order_return_detail.

Existing modules and themes potentially depend on them, and thirty bees itself reads returned quantities from order_return_detail.

Instead:

 
 
Existing / legacy
-----------------
tb_order_return
tb_order_return_detail
tb_order_return_state
tb_order_return_state_lang

New
---
tb_order_return_reason
tb_order_return_reason_lang

tb_order_return_resolution
tb_order_return_history
tb_order_return_attachment
tb_order_return_shipment
tb_order_return_reservation
tb_order_return_order
 

The old tables remain valid and continue to work.

Existing code asking:

 
 
$orderReturn->state
 

still gets a valid state.

Existing code reading:

 
 
order_return_detail
 

still sees the products/quantities involved.

The new tables simply enrich the RMA.

That is the safest BC architecture.


How I would implement it

  1. Modernize the RMA concept without breaking the legacy API. Keep OrderReturn as the RMA header. Keep OrderReturn::addReturnDetail() and existing queries working. Add services such as OrderReturnService, OrderReturnResolutionService, OrderReturnStockService, OrderReturnNotificationService and OrderReturnEligibilityService. Controllers should progressively become thin orchestration layers. The current FO still uses the traditional order-return controller/template architecture, so we can modernize internally while retaining the existing URLs and theme contract.
  2. Introduce structured reason codes. Instead of relying on the free-text question, create configurable reasons such as too_small, too_large, wrong_product, damaged, defective, not_as_expected, withdrawal, and other. Reasons should be multilingual and optionally enable/disable specific resolutions. For example, too_small can allow exchange/refund, while defective could allow replacement/refund/repair. The legacy question remains and becomes the customer's optional comment.
  3. Introduce resolutions per returned line. A single RMA may contain two products with different outcomes. One might be refunded while another is exchanged. I would therefore not put resolution_type directly on order_return. Create something along the lines of:
 
 
tb_order_return_resolution

id_order_return_resolution
id_order_return
id_order_detail
id_customization

quantity

reason_code
resolution_type

id_product_target
id_product_attribute_target

price_original
price_target
price_difference

status

date_add
date_upd
 

For a size exchange:

 
 
original:
Product 123
Combination 456 = Size M

resolution:
exchange

target:
Product 123
Combination 458 = Size L
 

This also lets us support colour exchanges or even replacement with another product later.

  1. Create a proper RMA state machine. Keep order_return.state because of BC, but add meaning and history behind it. I would use operational states approximately like:
 
 
Requested
↓
Awaiting approval
↓
Approved / Awaiting return
↓
In transit
↓
Received
↓
Inspection
↓
Approved for resolution
↓
Refund pending / Exchange pending
↓
Replacement shipped / Refunded
↓
Completed
 

There should also be:

 
 
Rejected
Cancelled by customer
Expired
 

The important change is tb_order_return_history. Currently the RMA essentially has one current state; we should know who changed it, when, and why.

For example:

 
 
22 Sep 10:14  Customer     Requested exchange M → L
22 Sep 10:21  Employee #4 Approved
23 Sep 16:42  System       Return shipment collected
25 Sep 09:10  Employee #3 Item received
25 Sep 09:12  Employee #3 Condition accepted
25 Sep 09:13  System       Size L allocated
25 Sep 15:03  Employee #3 Replacement dispatched
 

order_return.state simply remains the legacy projection of the current state.

  1. Make exchanges combination-aware. This is particularly important for your shops because sizes are combinations. When the customer selects Exchange, thirty bees should retrieve the combinations belonging to that product and show only valid alternatives. For example:
 
 
Exchange for:

○ 39
○ 40
● 41
○ 42 — unavailable
○ 43
 

There should be configuration controlling whether unavailable combinations are hidden or shown as unavailable.

For a simple size exchange, don't let the customer arbitrarily search the catalogue. Start with:

Same product → different combination

Later we can add:

Exchange for another product

as a separate feature.

  1. Add stock reservation specifically for exchanges. This is one of the most important parts. Imagine the customer requests XL, returns L five days later, and XL sells out in between. We should therefore support an optional reservation:
 
 
Requested exchange:
M → XL

XL available: 2

Reserve:
1 × XL
 

I would make the policy configurable:

 
 
Reserve replacement:
○ Never
○ When RMA is approved
○ Immediately when customer requests exchange
 

My default would be reserve on RMA approval.

The reservation should have an expiry and be released automatically if the return is cancelled/rejected/expires.

Importantly, a reservation isn't the same thing as decrementing physical stock.

  1. Handle the inbound and outbound legs separately. A return/exchange actually has two possible shipments:
 
 
Customer → shop
 

and, for an exchange:

 
 
Shop → customer
 

So don't put one tracking number directly onto the RMA. Use:

 
 
tb_order_return_shipment

direction = inbound/outbound
carrier
tracking_number
label/reference
shipping_cost
status
date_shipped
date_delivered
 

This would eventually integrate very nicely with tbspeedy.

For example your BO might show:

 
 
RETURN

Inbound
Speedy
Shipment: 123456789
Delivered 25 Sep

Replacement
Speedy
Shipment: 987654321
Dispatched 25 Sep
 

The exchange workflow I'd build

The customer's Order Details page could become:

 
 
ASICS Gel-Rocket
Size: 42
Qty: 1

[ Return / exchange item ]
 

Then:

 
 
What would you like to do?

● Exchange
○ Return for refund
○ Report a problem
 

Exchange:

 
 
Why?

● Too small
○ Too large
○ Prefer another colour
○ Other
 

Then:

 
 
Choose replacement

Size 40
Size 41
Size 42  ← purchased
Size 43
Size 44
Size 45
 

Then a summary:

 
 
YOU ARE RETURNING

ASICS Gel-Rocket
Size 42 × 1

YOU WILL RECEIVE

ASICS Gel-Rocket
Size 43 × 1

Price difference: €0.00
 

Customer clicks:

Request exchange

That creates the normal legacy RMA plus the new exchange information.


Same-price versus different-price exchanges

I would deliberately treat these differently.

Same price

This is the easy and common case:

 
 
M → L
€39.90 → €39.90

Difference: €0
 

No need to alter the original order.

The RMA handles:

 
 
return M
+
stock M
-stock L
ship L
 

The original commercial order remains untouched.

Replacement costs more

Example:

 
 
Original: €39.90
Replacement: €44.90

Customer owes: €5.00
 

Don't rewrite the old order.

The RMA should create a linked payment/new-order operation:

 
 
RMA #123
Original order #5000
Exchange difference: +€5
Related order/payment #5019
 

Replacement costs less

 
 
Original: €44.90
Replacement: €39.90

Refund due: €5
 

Use the existing refund/credit-slip machinery where possible.

This is much safer than altering historic order_detail records.


Do not convert the original order into the new size

This deserves emphasis.

If somebody bought:

 
 
Order #1000
Shoe / size 42
 

and exchanges for 43, do not change the original OrderDetail to size 43.

Order #1000 must forever show what was actually purchased:

 
 
Purchased: size 42
 

The RMA should show:

 
 
Returned: size 42
Replacement: size 43
 

That preserves invoices, statistics, payment reconciliation, audit history and module assumptions.


Back Office redesign

Rather than the old mostly tabular Merchandise Returns screen, I'd turn it into an operational RMA workspace.

Header:

 
 
RMA #000184
ORDER #001284
CUSTOMER
Created
Age
Current status
 

Main section:

 
 
RETURNED ITEM

[image]

hmlCORE XK POLY JERSEY
Size: M
Qty: 1

Reason:
Too small

Requested resolution:
Exchange

Replacement:
hmlCORE XK POLY JERSEY
Size: L

Stock:
7 available
1 reserved
 

Then actions:

 
 
Approve
Reject
Mark received
Inspect
Accept item
Reject item

Prepare exchange
Issue refund
Create credit slip

Generate return label
Generate replacement label
 

And below:

 
 
TIMELINE
 

with all history and communication.

This would be much more useful for actual daily operations.


Customer communication

We should stop making every RMA state dependent on manually written emails.

Introduce events/templates:

 
 
rma_created
rma_approved
rma_rejected
rma_waiting_return
rma_received
rma_exchange_reserved
rma_exchange_unavailable
rma_exchange_shipped
rma_refunded
rma_completed
 

Modules could hook into these events.

For example:

 
 
Hook::exec('actionOrderReturnStatusChanged', ...);
Hook::exec('actionOrderReturnCreated', ...);
Hook::exec('actionOrderReturnReceived', ...);
Hook::exec('actionOrderReturnResolutionCreated', ...);
Hook::exec('actionOrderReturnExchangeShipped', ...);
 

We should retain existing hooks/behaviour and add new hooks, never silently change the meaning of old ones.


Eligibility should also become a service

The current front controller exposes the configured return-day value to the template via PS_ORDER_RETURN_NB_DAYS.

Longer term, eligibility shouldn't be scattered through controllers/templates.

Something like:

 
 
$result = $returnEligibilityService->check(
    $order,
    $orderDetail,
    $customer
);
 

could return:

 
 
ReturnEligibilityResult {
    eligible: true,
    refundAllowed: true,
    exchangeAllowed: true,
    deadline: ...,
    reasons: [...],
}
 

This gives us a future path for:

 
 
Different return periods
Category exclusions
Customised products
Clearance items
B2B customers
Defective-product claims
Warranty workflow
Different countries
Different shops
 

without adding more conditionals everywhere.


RMA vs warranty

I would also future-proof the naming internally.

Not every after-sales case is really a merchandise return.

Eventually:

 
 
AfterSalesCase
    ├── Withdrawal / Return
    ├── Exchange
    ├── Warranty claim
    ├── Wrong shipment
    └── Damaged shipment
 

But I would not rename OrderReturn now because that would cause unnecessary compatibility problems.

Externally we can call the BO section:

Returns & Exchanges

while internally maintaining OrderReturn.


Attachments

Another worthwhile addition:

 
 
Customer attachment
Employee attachment
Courier document
Product-condition photo
 

For damaged or defective, BO configuration could say:

 
 
Require photograph: YES
 

For:

 
 
Too small
Too large
Changed mind
 

no photo would be needed.


Configuration

I would add a dedicated:

Orders → Returns & Exchanges → Settings

with settings such as:

Setting Example
Returns enabled Yes
Exchanges enabled Yes
Return period 14 days
Exchange reservation On approval
Reservation lifetime 7 days
Allow exchange to unavailable size No
Allow exchange to another product No
Customer pays return transport configurable
Merchant pays replacement transport configurable
Require approval before shipment Yes
Photos required for damage Yes

Multistore scope should work from the beginning.


API / module architecture

The services should be callable independently from the controllers:

 
 
OrderReturnService
OrderReturnEligibilityService
OrderReturnReasonService
OrderReturnResolutionService
OrderReturnExchangeService
OrderReturnReservationService
OrderReturnInventoryService
OrderReturnShipmentService
OrderReturnRefundService
OrderReturnNotificationService
OrderReturnHistoryService
 

That will make it much easier later to expose:

 
 
Webservice/API
mobile interface
courier integration
ERP integration
warehouse integration
 

without copying controller logic.

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...