Skip to content

New module: Maho_Rma #937

Description

@fballiano

Overview

Add a first-party returns management module so customers and guests can request returns from an order, exchange messages with the merchant, and have the resolution feed into the existing credit memo / order pipeline. No SaaS dependency, no external services, sits on the same building blocks as invoices, shipments, and credit memos.

Initial release: 1.0.0.

Design principles

  • Flat tables, no EAV. RMA is a sales document and follows the same convention as sales_flat_*.
  • Statuses, reasons, resolutions, item conditions are enum-style code constants. Reasons / resolutions / item conditions are also editable via a multi-line system.xml field for merchants who want to tweak them. No admin CRUD grids for taxonomy.
  • Per-product return rules through two new product attributes, not a separate rule engine.
  • Customer-facing URLs live under /sales/order/rma/* and guest URLs under /sales/guest/rma/*, matching the existing auth split for order views.
  • Resolution hooks (refund, replacement) prefill a draft credit memo or draft order. Admin still posts manually. Human in the loop.

Module layout

app/code/core/Maho/Rma/ with Block/, Helper/, Model/, controllers/, etc/, data/, sql/.
Declared in app/etc/modules/Maho_Rma.xml. Locale CSV at app/locale/en_US/Maho_Rma.csv.

etc/config.xml:

  • Module version 1.0.0
  • Model / block / helper / resource aliases under rma
  • Default config values
  • Email template definitions

etc/system.xml: settings group under Sales:

  • Enable RMA
  • Increment ID prefix
  • Default return window (days)
  • Allowed order statuses for return
  • Default reasons (multi-line)
  • Default resolutions (multi-line)
  • Default item conditions (multi-line)
  • Attachment allowed extensions, max file size, max files per RMA

etc/adminhtml.xml: ACL under admin/sales/rma.

Schema (install-1.0.0.php)

Four tables, no maho_ prefix:

  • rma (rma_id, increment_id, order_id, customer_id, customer_email, store_id, status, resolution, customer_comment, created_at, updated_at)
  • rma_item (rma_item_id, rma_id, order_item_id, qty_requested, qty_approved, qty_received, reason, condition)
  • rma_status_history (history_id, rma_id, status, comment, is_visible_on_front, is_customer_notified, created_by, created_at)
  • rma_attachment (attachment_id, history_id, file_name, file_path, file_size, mime_type)

FKs to sales_flat_order, sales_flat_order_item, customer_entity, core_store with the same on-delete semantics those tables use elsewhere.

Product attributes (data-install-1.0.0.php)

  • is_returnable (yes/no, default yes), added to the default attribute set
  • return_period_days (int, nullable, blank inherits global default)

State machine

8 states, defined as Maho_Rma_Model_Rma constants:

`pending`, `awaiting_info`, `approved`, `received`, `closed_refunded`, `closed_replaced`, `closed_rejected`, `closed_cancelled`

Resolution constants on Maho_Rma_Model_Resolution: `refund`, `replace`, `store_credit`, `repair`.

Transitions are validated in _beforeSave against a transition map. Invalid transitions throw Mage_Core_Exception.

Eligibility

Maho_Rma_Model_Eligibility returns the set of order items that can still be returned:

  • Order in an allowed status (config)
  • Within the return window (per-product return_period_days else global default)
  • Product is_returnable = yes
  • Qty not already covered by open or closed RMAs

The same helper powers the customer-facing form, the guest form, and the "Request return" button injection on order view pages.

Admin

  • Grid at /admin/rma/index with filters on status, resolution, store, customer email, order increment, date
  • Edit at /admin/rma/edit/{id}, tabs: Info, Items, History/Comments, Attachments
  • Comment form with visible to customer and notify customer checkboxes
  • Mass actions: change status, send notification, cancel
  • ACL: Maho_Rma::rma, Maho_Rma::manage, Maho_Rma::comment, Maho_Rma::resolve
  • _setForcedFormKeyActions(['save', 'delete', 'massStatus', 'massCancel'])

Customer frontend (routes via #[Maho\Config\Route])

  • GET /sales/order/rma/history
  • GET /sales/order/rma/create/{orderId} (form)
  • POST /sales/order/rma/create/{orderId} (submit)
  • GET /sales/order/rma/view/{id}
  • POST /sales/order/rma/comment/{id}

Plus template injection:

  • Mage_Sales order view: "Request return" button when eligibility returns non-empty items
  • Customer account left nav: "My returns" link
  • Comment submission uses mahoFetch(), no polling

Guest frontend

  • GET /sales/guest/rma/create/{orderHash}
  • POST /sales/guest/rma/create/{orderHash}
  • GET /sales/guest/rma/view/{id}
  • POST /sales/guest/rma/comment/{id}

Auth via the same hash/email pair the existing guest order lookup uses.
Template injection on sales/guest/view for the same "Request return" button.

Email notifications

Three templates, dispatched via Mage_Core_Model_Email_Template:

  • rma_new_admin: admin gets the new RMA
  • rma_status_update_customer: status changed
  • rma_comment_customer: admin posted a customer-visible comment

Triggered from observers on maho_rma_save_after and maho_rma_status_history_save_after.

Resolution hooks

Observer on maho_rma_status_change:

  • New status received + resolution refund: build a draft Mage_Sales_Model_Order_Creditmemo with approved qty / condition prefilled, store its id on the RMA. Admin posts manually.
  • New status received + resolution replace: prefill a draft order (admin reviews and submits).
  • store_credit and repair: flag-only in 1.0.0; wired later when store credit lands.

PDF return slip

Maho_Rma_Block_Pdf_Slip extending Mage_Core_Block_Pdf via DomPdf. Available from the RMA view page once status is approved. Includes RMA increment id, items, qty, reason, return address (from store config), barcode of the increment id.

Events exposed

  • maho_rma_save_before, maho_rma_save_after
  • maho_rma_status_change (carries old + new status)
  • maho_rma_status_history_save_after
  • maho_rma_attachment_save_after

Tests (Pest)

MahoBackendTestCase:

  • State transition validation
  • Eligibility calculation (window, is_returnable, qty already covered)
  • Credit memo prefill on resolution=refund + status=received
  • ACL on admin actions
  • Mass actions

MahoFrontendTestCase:

  • Customer create flow
  • Guest create flow (hash auth)
  • Comment visibility filter (is_visible_on_front)
  • Attachment size and extension validation
  • "Request return" button visibility against eligibility

Documentation

Add an RMA section to AGENTS.md in the same shape as the Routing and Date sections, covering:

  • Eligibility helper
  • State constants
  • Resolution constants
  • Event names
  • Per-product attributes

Suggested PR ordering

  1. Skeleton + schema + state machine + eligibility helper. No user-visible surface yet but the model is solid.
  2. Admin grid, edit, ACL, mass actions. Admin can create RMAs on customers' behalf.
  3. Customer frontend.
  4. Guest frontend.
  5. Email notifications.
  6. Resolution hooks (credit memo / replacement order prefill).
  7. PDF return slip.
  8. Documentation.

Tests land alongside the relevant PR, not at the end.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions