Discount Code CRUD Implementation Subtickets (Admin Panel)
Discount Code CRUD Implementation Subtickets (Admin Panel)
(Admin Panel)
Subticket 1: Create Discount Code
Description: Implement a Vue.js admin page with a form that allows the admin to create a new discount
code. The form should capture all required fields for the discount code and validate user input before
submission. On submitting valid data, a POST request will be sent to the REST API to create the discount
code. The UI should handle success (e.g., showing a confirmation and listing the new code) and error cases
(displaying validation errors).
Fields & Validation: The create form will include the following fields (all are required unless noted as
optional): - Name (string, required) – Internal name of the discount campaign (e.g., "Intro2025 New
Clients" ) 1 . - Discount Code (string, required) – The unique code used for redemption (e.g.,
INTRO50 ) 2 . Must be unique across all existing discount codes 3 (attempting to use a duplicate code
should result in an error "This code already exists." 4 ). - Discount Type (string, required) – Category of
discount (e.g., "onboarding" , "early_access" , or "introductory_period" ) 5 . If the type is
introductory_period , then Duration Type and Duration Value become mandatory inputs 3 . -
Discount Amount Type (string, required) – Determines how the discount is applied: "fixed" amount vs.
"percentage" off 6 . This affects validation of the value: - If fixed, Discount Value is an amount in BDT
(must be > 0) 7 . - If percentage, Discount Value is a percent (1–100; cannot exceed 100) 8 . - Discount
Value (number, required) – Numeric value of the discount to apply (e.g., 500 for 500 BDT off, or 20 for
20%) 9 . Must satisfy the rules above (e.g., >0 if fixed 7 ; ≤100 if percentage 8 ). - Discount Amount
Cap (number, optional) – If the discount is a percentage, this is an optional maximum amount (in BDT) that
the discount can deduct (e.g., max 1000 BDT off) 10 . Can be left null if no cap is desired. (Not applicable
for fixed discounts.) - Discount Number Cap (number, required) – The maximum number of distinct
companies that can use this code (global usage limit) 11 . For example, a value of 50 means only 50
companies can ever redeem this code. (Must be a positive integer.) - Single Use Cap (number, required) –
How many times a single company can use this code 12 . Default is 1 (each company can only use the
code once), but this can be set higher to allow multiple uses per company. Must be a positive integer
(typically ≥1). - Duration Type (string, conditional) – If the discount type is time-bound (e.g., an introductory
period discount), this specifies the duration basis: "months" or "invoices" 13 . (Required only for
introductory_period type; otherwise not used.) - Duration Value (number, conditional) – The number
of months or billing cycles the discount will apply, if using an introductory period type 14 . (Required if
discount_type is introductory_period ; otherwise ignored.) - Expiration Date (string, optional) –
The date after which the code expires and is no longer valid (format YYYY-MM-DD ) 15 . If provided, it must
be today’s date or a future date (cannot be set in the past – e.g., setting a past date should trigger an error
"Expiration date must be today or later." 16 ). - Companies (array of strings or IDs, optional) – An optional
list of company identifiers that are allowed to use this code 17 . If specified, only those companies can
redeem the code; otherwise, the code is usable by any company 18 . - Status (string, optional) – The
activation status of the code, either "Active" or "Inactive" 19 . By default, new discount codes are
Active 20 . (The admin may choose to create the code as Inactive if they don't want it usable immediately.)
1
API Endpoint: POST /api/discount-codes – Creates a new discount code.
- Request Headers: Content-Type: application/json (authentication/authorization assumed
handled globally).
- Request Body: JSON object containing the fields above.
- Response: On success, returns a 201 Created with the newly created discount code object (including its
generated id and default/derived fields). On validation failure, returns appropriate error status (e.g., 400)
with error details.
Request JSON Schema & Example: The JSON request should include all required fields. For example:
{
"name": "Intro2025 New Clients",
"discount_code": "INTRO50",
"discount_type": "onboarding",
"discount_amount_type": "percentage",
"discount_value": 50,
"discount_amount_cap": 1000,
"discount_number_cap": 100,
"single_use_cap": 1,
"duration_type": null,
"duration_value": null,
"expiration_date": "2025-12-31",
"companies": [],
"status": "Active"
}
In this example, INTRO50 is a 50% off code (capped at 1000 BDT) for onboarding campaigns, usable by up
to 100 companies, one use each, expiring end of 2025. The duration_type and duration_value are
not applicable (hence null) since this is not an introductory period discount. The code is being created as
Active.
Response JSON Schema & Example: On success, the API will return the created resource. For example:
{
"id": 123,
"name": "Intro2025 New Clients",
"discount_code": "INTRO50",
"discount_type": "onboarding",
"discount_amount_type": "percentage",
"discount_value": 50,
"discount_amount_cap": 1000,
"discount_number_cap": 100,
"single_use_cap": 1,
"duration_type": null,
"duration_value": null,
2
"expiration_date": "2025-12-31",
"companies": [],
"status": "Active",
"created_at": "2025-05-26T12:00:00Z",
"created_by": "adminUser123"
}
The response includes a unique id for the discount code and metadata like created_at timestamp and
possibly the admin user who created it. (The exact fields may vary; the frontend should use whatever the
API returns to update the UI.)
Acceptance Criteria: - The Create Discount Code page presents a form with all the fields listed above. The
user cannot submit until all required fields are filled with valid values (all fields are required unless marked
optional) 3 . - Validation is enforced on the client side and reflected from server responses: - If any
required field is missing or empty, the form should display a validation error near that field (e.g., “This field
is required” for name or code) 21 . - The discount_code field must be unique. If the admin enters a code
that already exists, the form should not allow submission or the API should return an error. In case of a
duplicate, display an error message (e.g., “This code already exists.”) 4 . - If discount_amount_type is
"fixed" and discount_value ≤ 0, prevent submission. Display an error like “Discount value must be
greater than 0.” 7 . - If discount_amount_type is "percentage" and discount_value > 100, prevent
submission. Display an error like “Percentage cannot exceed 100.” 22 . - If an Expiration Date is provided
and it is before today, the form should show an error “Expiration date must be today or later.” 16 . - If
discount_type is "introductory_period" , the form must require duration_type and duration_value
to be selected/filled (and show errors if they are missing) 23 . - The form should provide helpful hints or
tooltip text for fields (e.g., explaining fixed vs percentage, meaning of caps, etc.) and disable or hide fields
that are not applicable based on selections. For example, if Discount Amount Type is "fixed" , the Discount
Amount Cap field can be disabled or marked as not applicable. - The Companies field (if present) might be a
multi-select dropdown or tags input. The admin can optionally select one or more companies to restrict the
code. If companies are selected, it should be clear that the discount will apply only to those companies (and
this restriction should be saved) 18 . - Upon clicking “Create” with valid inputs, a POST request is sent to /
api/discount-codes . While the request is in progress, the UI should indicate a loading state. If the
request succeeds: - The admin is shown a confirmation (e.g., a success message or toast notification). - The
new discount code should appear in the discount code list (either by navigating to the list view or updating
the list in-place). By default, the created code’s status should be Active 20 . - The form can be cleared/reset
for a new entry or the UI can redirect to the list view showing the new entry. - If the API responds with
validation errors (HTTP 400 with error details), the UI should display the specific error messages next to the
corresponding fields. Examples: - Duplicate code error from server should map to the discount_code field
error (“This code already exists.”). - Any server-side validation messages for numeric fields (value or caps) or
date should be shown (as described above). - All client-side validations should be in place (e.g., numeric
fields accept only numbers, date picker or input for dates, etc.), but the frontend must also handle any error
responses from the server to cover edge cases (e.g., race conditions where two codes are created
simultaneously causing a duplicate). - The implementation should be done in a modular Vue component,
and the code should be structured for easy maintenance. Use appropriate Vue form binding and validation
libraries as needed. The form should be accessible and usable on modern browsers.
3
Subticket 2: List/View Discount Codes
Description: Implement a Vue.js admin page that displays a list of all discount codes in a tabular format.
This page will allow admins to view existing discount codes and their details, search/filter through them,
and navigate to edit or delete actions. It should fetch data from the REST API and present it in a user-
friendly table with sorting and filtering capabilities.
UI Components: The page will consist of a table of discount codes and controls for filtering: - A table listing
each discount code and key attributes (columns detailed below). - A search bar and/or filter controls to filter
the list by code name or type. - Buttons or links for actions: Create New Discount Code, Edit, Delete for
each entry.
[
{
"id": 123,
"name": "Intro2025 New Clients",
"discount_code": "INTRO50",
"discount_type": "onboarding",
"discount_amount_type": "percentage",
"discount_value": 50,
"discount_amount_cap": 1000,
"discount_number_cap": 100,
"single_use_cap": 1,
"duration_type": null,
"duration_value": null,
"expiration_date": "2025-12-31",
"companies": [],
"status": "Active",
"usage_count": 0,
"created_at": "2025-05-26T12:00:00Z"
},
{
"id": 124,
"name": "EarlyBird Q3 Promo",
"discount_code": "EARLYQ3",
"discount_type": "early_access",
4
"discount_amount_type": "fixed",
"discount_value": 300,
"discount_amount_cap": null,
"discount_number_cap": 50,
"single_use_cap": 1,
"duration_type": null,
"duration_value": null,
"expiration_date": "2025-09-30",
"companies": ["COMP1001","COMP1002"],
"status": "Inactive",
"usage_count": 10,
"created_at": "2025-04-10T09:00:00Z"
}
]
In this example, two discount codes are returned. Each object includes fields such as status and an
example usage_count (how many companies have used it so far, if provided by the API). The second code
is a fixed amount BDT 300 off, limited to 50 companies, and currently Inactive (with 10 uses already).
Table Columns: The list view should display important fields for each discount code in columns. Suggested
columns: - Name – The internal name of the discount (e.g., "Intro2025 New Clients"). - Code – The
redemption code string (e.g., INTRO50). - Type – The category/type of discount (e.g., Onboarding, Early
Access, etc.). - Discount – The discount amount and kind. This can combine discount_amount_type and
discount_value (and cap if percentage). For example: “50% (max 1000 BDT)” or “300 BDT” for fixed amounts.
- Usage – (If available) How many times the code has been used vs the cap. For example, “0 / 100 uses” for a
code that 0 out of 100 allowed uses have been utilized, or “10 / 50 uses” for a code used 10 times out of a 50
use cap. (This requires the usage_count from backend and knowledge of the caps.) - Expiration Date –
The date the code expires, or “N/A” if no expiration is set. - Status – Active or Inactive state of the code
(could be shown as a badge or toggle). - Actions – Edit and Delete controls for each code (e.g., an edit pencil
icon and a trash icon).
Acceptance Criteria: - On navigating to the Discount Codes List page, the application sends a GET request
to /api/discount-codes to retrieve the list of discount codes. The data is displayed in a table format,
with each code’s information populating one row. - The table displays the columns as described above,
allowing the admin to see key details at a glance (code, type, value, expiration, status, etc.). - Implement
search/filter controls: - A text search box should allow filtering the list by discount code or name (case-
insensitive substring match). As the user types a query, the list should update to show only codes whose
name or code match the query. - A filter (dropdown or set of tabs) for discount type can be provided to
quickly filter codes by their discount_type (e.g., show only "onboarding" codes, only "early_access", etc.).
This can work in conjunction with the text search. - Implement sorting on key columns: - The admin should
be able to sort the list by at least one or two important columns, such as Expiration Date (soonest/oldest) or
Usage count (most used/least used). For example, clicking the "Expiration Date" header could toggle sorting
by date, and clicking "Usage" toggles sorting by usage numbers. Sorting by Code or Name alphabetically is
also useful. - By default, the list could be sorted by creation date or name; the exact default can be decided,
but the user should have control to sort by columns as needed. - If there are no discount codes in the
system, the UI should display a friendly empty state message (e.g., "No discount codes available. Click 'New
5
Discount Code' to create one.") instead of an empty table. - The page should include a prominent “New
Discount Code” button. Clicking this navigates to the Create Discount Code form (subticket 1). - Each row in
the table should have an Edit action. Clicking edit opens the Update Discount Code form (subticket 3) for
that specific code, where the admin can modify its details. - Each row should also have a Delete action (e.g.,
a trash can icon). Clicking delete will prompt for confirmation and, if confirmed, remove the code (subticket
4). - The list should reflect the latest state of the data: - If a new code is created, it should appear in the list
(e.g., after creation, the app navigates back to this list or updates the list via state). - If a code is edited, the
changes should be visible in the table (e.g., name or status updates immediately). - If a code is deleted, it
should be removed from the list. - The frontend should handle error states: - If the GET request fails
(network or server error), an error message should be shown (e.g., "Failed to load discount codes. Please try
again."). - If filter or search yields no results, display a "No matching discount codes found" message
(distinct from the truly empty state when no codes at all). - Ensure the UI is clean and easy to read: use
formatting for currency vs percentage (e.g., add % sign or currency symbol accordingly), and perhaps
color-code or badge the status (Active could be green, Inactive grey/red). - The implementation should be
done in a responsive manner if the admin panel requires it, ensuring the table is scrollable or reflows on
smaller screens.
Pre-fill and Data Loading: When the admin navigates to the edit form (e.g., via clicking "Edit" on a specific
code in the list), the app should load the current details of that discount code. This can be done by using the
data from the list or by calling GET /api/discount-codes/{id} to fetch the latest details. The form
fields should then be pre-filled with the code’s existing values.
Fields: The editable fields and their validation rules are the same as in the Create form (see subticket 1 for
field definitions). The admin should be able to update: - Name, Discount Code, Discount Type, Discount
Amount Type, Discount Value, Discount Amount Cap, Discount Number Cap, Single Use Cap, Duration
Type/Value, Expiration Date, and Status. - All the validation rules apply on update as well (required fields,
numeric limits, date not in past, etc.). For example, the discount code must remain unique – changing it to a
code that conflicts with another should produce an error 24 . Similarly, an invalid expiration date or
discount value should be caught and disallowed, just as in creation. - Usage-based restrictions: If the
discount code has already been used by some companies ( usage_count > 0 or otherwise flagged by the
backend), certain fields may not be editable. For instance, if a code has been used, the business might
disallow changing the discount_value or discount_type. The UI should follow these rules: - If the code has
no usage yet, all fields are fully editable (it’s effectively the same as creating a new code, but pre-filled). - If
the code has been used at least once, the UI should either disable editing for fields that should not change
post-use or warn the user about potential impacts. (For example, perhaps name and expiration could still be
edited, but value or code might be locked if usage > 0. This depends on defined business rules.) According to the
story, the coupon is only fully editable if it hasn’t been used, otherwise only limited edits are permitted 25 .
6
The interface should reflect this by disabling fields or showing a notice if the code has usage (e.g., “Note:
This code has already been used by some customers; certain fields cannot be changed”). - Status: The
admin can toggle the code’s status between Active and Inactive 19 . This is an important action: - If setting
the status to Inactive, the UI should ask for confirmation. Optionally (if supported by the backend), provide
options on how to handle existing usages of the code. For example, the system might allow the admin to
choose between: 1. Deactivate future use only – existing subscriptions or uses remain honored, but no
new usage allowed. 2. Deactivate and cancel existing – immediately revoke the discount from any
companies currently using it 26 .
This could be a prompt when the admin tries to inactivate a code that is in use, offering these choices. If not
implementing the full prompt, at minimum warn that inactivating will stop any new usage of the code. - If
setting the status back to Active (re-activating a code), simply allow it and update the status – the code
becomes available for use again. - The form should visually indicate if a field is not editable (grayed out) due
to usage or other rules. Also, any logic such as requiring Duration fields for introductory_period, or
enabling/disabling the Amount Cap based on Amount Type, should work just like in the create form.
Request JSON Example: For instance, to update code ID 123, a PUT to /api/discount-codes/123
might include a body like:
{
"name": "Intro2025 New Clients (extended)",
"discount_code": "INTRO50",
"discount_type": "onboarding",
"discount_amount_type": "percentage",
"discount_value": 50,
"discount_amount_cap": 1500,
"discount_number_cap": 100,
"single_use_cap": 1,
"duration_type": null,
"duration_value": null,
"expiration_date": "2026-03-31",
"companies": [],
"status": "Active"
}
This example shows changing the name, extending the expiration_date, and increasing the
discount_amount_cap, for instance. (The discount_code and other fields remained the same; all fields are
sent for completeness.)
7
Response JSON Example: On success, the API might return the updated object:
{
"id": 123,
"name": "Intro2025 New Clients (extended)",
"discount_code": "INTRO50",
"discount_type": "onboarding",
"discount_amount_type": "percentage",
"discount_value": 50,
"discount_amount_cap": 1500,
"discount_number_cap": 100,
"single_use_cap": 1,
"duration_type": null,
"duration_value": null,
"expiration_date": "2026-03-31",
"companies": [],
"status": "Active",
"updated_at": "2025-08-01T09:00:00Z",
"updated_by": "adminUser123"
}
The response reflects the updated fields (new expiration date, cap, name) and includes metadata like
updated_at timestamp.
Acceptance Criteria: - When the admin selects "Edit" for a discount code (e.g., from the list), the application
loads the Update Discount Code form. The form is populated with the current values of that discount code
(matching what is on record). - The form allows editing of the same fields as in creation, with the same
client-side validations: - Required fields cannot be left blank (enforce presence). - Field formats and rules are
the same (e.g., numeric fields only accept numbers, date picker for date, etc.). - All the business rules apply:
for example, the code must remain unique. If the admin changes the discount_code to one that conflicts
with another existing code, the API should return an error and the UI should show “This code already
exists.” (Uniqueness must be enforced on update as well) 24 . - If the admin changes
discount_amount_type or discount_type, the form should dynamically adjust (e.g., if they switch from
percentage to fixed, the cap field might be cleared/disabled; if they switch a code to
introductory_period type, the duration fields should appear and be required). - If the discount code
has never been used (usage count is 0): - The admin can edit all fields freely. Submitting will simply update
the code. - If the discount code has been used by one or more companies: - The system should restrict
certain changes. For example, it may disable editing of the discount_value or code, since changing the
value after usage could be problematic 25 . It may allow changing less critical fields (like Name or Expiration
Date) or just status toggling. The exact restrictions should follow business rules, but the UI must reflect
them (either by disabling fields or by providing a clear warning). - If the admin attempts to make a
disallowed change on a used code, the form should prevent it or the API should reject it. In either case, a
message should inform the admin (e.g., “Cannot change discount value after code has been used”). - If only
limited edits are allowed post-use, ensure those work (e.g., maybe allowing extending the expiration date
for a used code, but nothing else). - The Status toggle should be editable. The admin can set the code
Active or Inactive: - If the admin toggles the status to Inactive and clicks save, the UI should confirm this
8
action (since inactivating could affect active users). If the code is currently in use (companies have
redeemed it), present the admin with options or at least a warning as described above (e.g., “Deactivating
this code will prevent any new usage” and if possible “Do you also want to cancel existing discounts for
users who have used this code?”) 26 . The choice made (if offered) should be sent to the backend (this
might be an additional API call or parameter, depending on implementation). - If toggled to Inactive, after
saving, the code’s status should show as Inactive in the list and it should no longer be accepted in the
system for new registrations/subscriptions 27 . - If toggled back to Active, after saving, the code becomes
usable again (assuming it hasn’t expired and usage caps not exceeded). - Upon clicking “Save” (or “Update”)
with valid changes: - A PUT request is sent to /api/discount-codes/{id} . The app should show a
loading indicator during the request. - If the API call is successful (200 OK), a success message is shown
(e.g., “Discount code updated successfully”). The changes should immediately reflect in the UI: - If the app
routes back to the list, the list should show the updated details (e.g., new name, new status, extended
expiration). - If the app stays on the same page (e.g., in a modal), the form fields should update to the saved
values (and possibly disable further editing until more changes are made). - If the API returns an error
(validation or otherwise): - The form should display the errors similarly to the create form. For example, if
the error is that the code is duplicate or invalid, show it near the field. - No changes should be persisted if
an error occurs. The admin can correct the input and try again. - Ensure that canceling out of the edit (if
there's a Cancel button) does not save changes. If using Vue Router, navigation guards might prompt if
there are unsaved changes and the user tries to leave. - The edit form should be implemented possibly
reusing the same component as the create form, but in "edit mode" (prefilled data and different submit
action). This avoids duplicate code and ensures consistency in validation and layout. - The UI should be
intuitive: e.g., the page title or header should indicate “Edit Discount Code”, and maybe display the code
being edited. This helps the admin confirm they are editing the correct item.
UI Flow: In the discount codes table, each row has a delete option (e.g., a trash icon or “Delete” button).
When clicked: - The UI should prompt the admin with a confirmation dialog: e.g., “Are you sure you want to
delete the discount code INTRO50? This action cannot be undone.” The code or name should be mentioned
in the confirmation for clarity. - Only if the admin confirms (e.g., clicks “Yes, Delete”) will the deletion
proceed.
9
{ "message": "Discount code deleted successfully." }
If the code cannot be deleted due to business rules (e.g., it’s already used), the response might be
something like:
(The exact format depends on the API; the frontend should handle both no-content and JSON message
scenarios.)
Acceptance Criteria: - When the admin confirms deletion of a discount code: - The frontend sends a
DELETE request to /api/discount-codes/{id} for the specific code. - A loading state or disabled state
should be applied to the confirmation dialog/buttons to prevent duplicate requests while waiting for the API
response. - If the discount code has never been used (usage count = 0 or no associated usage): - The API
will allow deletion 28 . The frontend should then remove the deleted code from the list and show a success
notification (e.g., “Discount code XYZ has been deleted.”). - The list of codes should update (either by
removing the item locally or by refetching the list from the server) so that the deleted code is no longer
visible. - If the discount code has already been used by one or more companies: - According to
requirements, deletion in this case is not allowed 28 . The UI should ideally prevent this scenario. For
example, the Delete action could be hidden or disabled for codes that have a usage_count > 0. Alternatively,
if the user attempts to delete such a code, the frontend should display a warning and require extra
confirmation. - If a deletion is attempted on a used code and the backend responds with an error (e.g.,
400 Bad Request with a message), the UI must handle it gracefully: - Do not remove the code from the
list. - Display an error message to the admin, for example: “Cannot delete a used discount code.” 29 . The
message should clarify that because the code has been used, it cannot be deleted. - Suggest alternative
action: the UI might inform the admin that they can disable/inactivate the code instead of deletion (since a
"soft delete" via inactivation is allowed) 30 . - The system should never silently fail; the admin should clearly
know that the deletion didn’t happen and why. - The confirmation dialog should be clear about the
consequences. If the code is unused, a simple confirm is fine. If the code has been used (and if the UI hasn't
disabled the delete action), the confirmation could reiterate “This code has already been redeemed by some
customers; deleting it is not allowed. You may need to deactivate it instead.” and possibly not even allow the
confirm to proceed. - After a successful deletion: - Ensure any state in the application (Vuex store or
component state) is updated to remove the code. If the app was caching the list of codes, update that
cache. - If the user is viewing details of the deleted code (unlikely in this flow, since deletion is from the list),
navigate them away (e.g., back to list) since that detail no longer exists. - If the delete operation fails due to
network/server issues (not business rule, but say a 500 error), show a general error message (e.g., “Deletion
failed due to a server error. Please try again later.”). The code should remain in the list in this case. - The
implementation should include appropriate user feedback: use a modal for confirmation, use toasts or
alerts for success/error messages, etc., to ensure the admin knows what happened. - Audit logging (if
visible in UI) is outside the scope of the immediate UI change, but the story indicates that each delete action
would be logged with the admin’s identity and timestamp 31 . (This is handled in backend, but if there's an
audit view, the deletion would appear there.)
By completing these subtasks, the frontend will fully support Create, Read/List, Update, and Delete
operations for discount codes using a RESTful backend, with all necessary fields, validations, and user
10
interactions covered. Each component/page should use mock API endpoints as specified, allowing a
frontend engineer to implement and test the functionality even before the real backend is available. All
acceptance criteria must be met to consider each feature complete and ready for integration testing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
31 RE-8580.doc
file://file-UarV2ScpeugDEFE9fqCbKp
11