diff --git a/_reference/commission-based-cut-manual-test-plan.md b/_reference/commission-based-cut-manual-test-plan.md new file mode 100644 index 000000000..df94cc3f4 --- /dev/null +++ b/_reference/commission-based-cut-manual-test-plan.md @@ -0,0 +1,424 @@ +# Commission-Based Cut Feature Manual Test Plan + +## Purpose +Use this guide to manually test the commission-based cut feature from an end-user point of view. + +This plan is written for a non-technical tester. Follow the steps exactly as written and mark each scenario as Pass or Fail. + +## What You Need Before You Start +- A login that can open `Manage my Shop`, `Jobs`, and `Time Tickets`. +- At least 2 active employees in the shop. +- At least 1 converted repair order that already has labor lines on it. +- If possible, use a simple test job where the labor sale rates are easy to calculate. +- A notebook, spreadsheet, or screenshot folder to record what happened. + +## Recommended Easy-Math Test Data +If you can choose your own test job, use something simple like this: + +- Body sale rate: `$100.00` +- Refinish sale rate: `$120.00` +- Mechanical sale rate: `$80.00` +- 1 Body labor line with `10.0` hours +- 1 Refinish labor line with `4.0` hours + +This makes the expected payout easy to check: + +- `40%` of `$100.00` = `$40.00` +- `30%` of `$120.00` = `$36.00` + +## Important Navigation Notes +- Team setup is under `Manage my Shop` > `Employee Teams`. +- Team assignment happens on the job line grid in the `Team` column. +- Automatic payout happens from the job's `Labor Allocations` card using the `Pay All` button. +- If your shop uses task presets, the `Flag Hours` button can preview the payout method before committing tickets. + +--- + +## Scenario 1: Create a Simple Commission Team +### Goal +Confirm a team member can be set to commission and saved successfully. + +### Steps +1. Sign in. +2. Click `Manage my Shop`. +3. Click the `Employee Teams` tab. +4. Click `New Team`. +5. In `Team Name`, type `Commission Team Test`. +6. Make sure `Active` is turned on. +7. In `Max Load`, enter `10`. +8. Click `New Team Member`. +9. In `Employee`, choose an active employee. +10. In `Allocation %`, enter `100`. +11. In `Payout Method`, choose `Commission %`. +12. In each commission field that appears, enter a value. +13. For the main labor types you plan to test, use these values: +14. Enter `40` for Body. +15. Enter `30` for Refinish. +16. Enter `25` for Mechanical. +17. Enter `20` for Frame. +18. Enter `15` for Glass. +19. Fill in the remaining commission boxes with any valid number from `0` to `100`. +20. Click `Save`. + +### Expected Result +- The team saves successfully. +- The team stays visible in the Employee Teams list. +- The team member card shows a `Commission` tag. +- The `Allocation Total` shows `100%`. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 2: Allocation Total Must Equal 100% +### Goal +Confirm the system blocks a team that does not total exactly 100%. + +### Steps +1. Stay on the same team. +2. Change `Allocation %` from `100` to `90`. +3. Click `Save`. +4. Change `Allocation %` from `90` to `110`. +5. Click `Save`. +6. Change `Allocation %` back to `100`. +7. Click `Save` again. + +### Expected Result +- When the total is `90%`, the system should not save. +- When the total is `110%`, the system should not save. +- The page should show that the allocation total is not correct. +- When the total is set back to `100%`, the save should succeed. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 3: The Same Employee Cannot Be Added Twice +### Goal +Confirm the same employee cannot appear twice on one team. + +### Steps +1. Open the same team again. +2. Click `New Team Member`. +3. Choose the same employee already used on the team. +4. Enter any valid allocation amount. +5. Choose `Commission %`. +6. Fill in all required commission fields. +7. Click `Save`. + +### Expected Result +- The system should block the save. +- The team should not save with the same employee listed twice. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 4: Switching Between Hourly and Commission Changes the Input Style +### Goal +Confirm the rate section changes correctly when the payout method changes. + +### Steps +1. Open the same team again. +2. On the team member row, change `Payout Method` from `Commission %` to `Hourly`. +3. Look at the rate fields that appear. +4. Change `Payout Method` back to `Commission %`. +5. Look at the rate fields again. + +### Expected Result +- In `Hourly` mode, the rate boxes should behave like money/rate fields. +- In `Commission %` mode, the rate boxes should behave like percentage fields. +- The screen should clearly show you are editing the correct type of value. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 5: Boundary Values for Commission % +### Goal +Confirm the feature accepts valid boundary values and blocks invalid ones. + +### Steps +1. Open the team again. +2. In one commission box, enter `0`. +3. In another commission box, enter `100`. +4. Click `Save`. +5. Try to type a value above `100` in one of the commission boxes. +6. Try to type a negative value in one of the commission boxes. + +### Expected Result +- `0` should be accepted. +- `100` should be accepted. +- Values above `100` should not be allowed or should fail validation. +- Negative values should not be allowed or should fail validation. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 6: Inactive Teams Should Not Be Offered for New Assignment +### Goal +Confirm inactive teams do not appear as normal team choices. + +### Steps +1. Open the team again. +2. Turn `Active` off. +3. Click `Save`. +4. Open a converted repair order. +5. Go to the job lines area where the `Team` column is visible. +6. Click inside the `Team` field on any labor line. +7. Open the team drop-down list. +8. Look for `Commission Team Test`. +9. Go back to `Manage my Shop` > `Employee Teams`. +10. Turn `Active` back on. +11. Click `Save`. +12. Return to the same job line and open the `Team` drop-down again. + +### Expected Result +- When the team is inactive, it should not appear as a normal assignment choice. +- After turning it back on, it should appear again. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 7: Assign the Commission Team to a Labor Line +### Goal +Confirm the team can be assigned to a job line from the job screen. + +### Steps +1. Open a converted repair order that has labor lines. +2. Find a labor line in the job line grid. +3. In the `Team` column, click the blank area or the current team name. +4. From the drop-down list, choose `Commission Team Test`. +5. Click outside the field so it saves. +6. Repeat for at least 1 Body line and 1 Refinish line if both exist. + +### Expected Result +- The selected team name should appear in the `Team` column. +- The assignment should stay in place after the screen refreshes. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 8: Pay All Creates Commission-Based Tickets +### Goal +Confirm `Pay All` creates time tickets using the commission rate, not a flat hourly rate. + +### Steps +1. Use a converted repair order that has: +2. At least 1 labor line assigned to `Commission Team Test`. +3. Known labor sale rates on the job. +4. No existing time tickets for the same employee and labor type. +5. Open that repair order. +6. Go to the labor/payroll area where the `Labor Allocations` card is visible. +7. Write down the following before you click anything: +8. The labor type on the line. +9. The sold labor rate for that labor type. +10. The hours on that line. +11. The commission % you entered for that labor type on the team. +12. Click `Pay All`. +13. Wait for the success message. +14. Look at the `Time Tickets` list on the same screen. +15. Find the new ticket created for that employee. + +### Expected Result +- The system should show `All hours paid out successfully.` +- A new time ticket should appear. +- The ticket rate should equal: +- `sale rate x commission %` +- Example: if Body sale rate is `$100.00` and commission is `40%`, the ticket rate should be `$40.00`. +- The productive hours should match the assigned labor hours for that employee. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 9: Different Labor Types Use Different Commission Rates +### Goal +Confirm the feature uses the correct commission % for each labor type. + +### Steps +1. Use a job that has at least: +2. One Body labor line. +3. One Refinish labor line. +4. Make sure both lines are assigned to `Commission Team Test`. +5. Confirm your team is set up like this: +6. Body = `40%` +7. Refinish = `30%` +8. Open the job's `Labor Allocations` area. +9. Click `Pay All`. +10. Review the new time tickets that are created. + +### Expected Result +- The Body ticket should use the Body commission %. +- The Refinish ticket should use the Refinish commission %. +- Example: +- If Body sale rate is `$100.00`, Body payout rate should be `$40.00`. +- If Refinish sale rate is `$120.00`, Refinish payout rate should be `$36.00`. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 10: Mixed Team With Commission and Hourly Members +### Goal +Confirm one team can contain both commission and hourly members, and each person is paid correctly. + +### Steps +1. Open `Manage my Shop` > `Employee Teams`. +2. Open `Commission Team Test`. +3. Edit the first team member: +4. Keep Employee 1 as `Commission %`. +5. Change `Allocation %` to `60`. +6. Make sure Body commission is still `40`. +7. Add a second team member. +8. Choose a different active employee. +9. Set `Allocation %` to `40`. +10. Set `Payout Method` to `Hourly`. +11. Enter an hourly rate for each required labor type. +12. For Body, use `$25.00`. +13. Fill in the other required hourly boxes with valid values. +14. Make sure the total allocation shows `100%`. +15. Click `Save`. +16. Assign this team to a Body line with `10.0` hours. +17. Click `Pay All`. +18. Review the new time tickets. + +### Expected Result +- Employee 1 should receive `60%` of the hours at the commission-derived rate. +- Employee 2 should receive `40%` of the hours at the hourly rate you entered. +- Example with a 10-hour Body line and `$100.00` sale rate: +- Employee 1 should get `6.0` hours at `$40.00`. +- Employee 2 should get `4.0` hours at `$25.00`. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 11: Pay All Only Adds the Remaining Hours +### Goal +Confirm `Pay All` does not duplicate hours that were already paid. + +### Steps +1. Use a job with one Body line assigned to `Commission Team Test`. +2. Make sure the line has `10.0` hours. +3. In the `Time Tickets` card, click `Enter New Time Ticket`. +4. Create a manual time ticket for the same employee and the same labor type. +5. Enter `4.0` productive hours. +6. Save the manual time ticket. +7. Go back to the `Labor Allocations` card. +8. Click `Pay All`. +9. Review the new ticket that is created. + +### Expected Result +- The system should only create the remaining unpaid hours. +- In this example, it should add `6.0` hours, not `10.0`. +- The payout rate should still use the current commission-based rate. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 12: Unassigned Labor Lines Should Block Automatic Payout +### Goal +Confirm `Pay All` does not silently pay lines that do not have a team assigned. + +### Steps +1. Open a converted repair order with at least 2 labor lines. +2. Assign `Commission Team Test` to one line. +3. Leave the second labor line with no team assigned. +4. Go to the `Labor Allocations` card. +5. Click `Pay All`. + +### Expected Result +- The system should not quietly pay everything. +- You should see an error telling you that not all hours have been assigned. +- The unassigned line should still need manual attention. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Scenario 13: Flag Hours Preview Shows the Correct Payout Method +### Goal +If your shop uses task presets, confirm the preview shows `Commission` for commission-based tickets. + +### Steps +1. Open a converted repair order. +2. Go to the `Time Tickets` card. +3. Click `Flag Hours`. +4. Choose a task preset. +5. Wait for the preview table to load. +6. Review the `Payout Method` column in the preview. +7. If the preview includes more than one employee, review each row. + +### Expected Result +- The preview table should load without error. +- Rows for commission-based employees should show `Commission`. +- Rows for hourly employees should show `Hourly`. +- If there are unassigned hours, a warning should appear. + +### Record +- [ ] Pass +- [ ] Fail +- Notes: + +--- + +## Quick Regression Checklist +- [ ] I can create a commission-based team. +- [ ] Allocation must total exactly 100%. +- [ ] The same employee cannot be added twice to one team. +- [ ] Inactive teams do not appear for normal assignment. +- [ ] A team can be assigned to job lines from the `Team` column. +- [ ] `Pay All` creates commission-based tickets correctly. +- [ ] Different labor types use different commission percentages. +- [ ] Mixed commission and hourly teams calculate correctly. +- [ ] `Pay All` only creates the remaining unpaid hours. +- [ ] Unassigned labor lines stop automatic payout. +- [ ] `Flag Hours` preview shows the correct payout method. + +## Tester Sign-Off +- Tester name: +- Test date: +- Environment: +- Overall result: +- Follow-up issues found: diff --git a/_reference/localEmailViewer/README.md b/_reference/localEmailViewer/README.md index 3f8a6fc71..6054686fd 100644 --- a/_reference/localEmailViewer/README.md +++ b/_reference/localEmailViewer/README.md @@ -1,7 +1,33 @@ -This will connect to your dockers local stack session and render the email in HTML. +This app connects to your Docker LocalStack SES endpoint and gives you a local inbox-style viewer +for generated emails. + +```shell +npm start +``` + +Or: ```shell node index.js ``` -http://localhost:3334 +Open: http://localhost:3334 + +Features: + +- Manual refresh +- Live refresh with adjustable polling interval +- Search across subject, addresses, preview text, and attachment names +- Expand/collapse all messages +- Rendered HTML, plain-text, and raw MIME views +- Copy raw MIME source +- New-message highlighting plus fetch timing and parse-error stats + +Optional environment variables: + +```shell +PORT=3334 +SES_VIEWER_ENDPOINT=http://localhost:4566/_aws/ses +SES_VIEWER_REFRESH_MS=10000 +SES_VIEWER_FETCH_TIMEOUT_MS=5000 +``` diff --git a/_reference/localEmailViewer/index.js b/_reference/localEmailViewer/index.js index 1dd17f779..15b7736a9 100644 --- a/_reference/localEmailViewer/index.js +++ b/_reference/localEmailViewer/index.js @@ -1,96 +1,1011 @@ -// index.js - import express from "express"; import fetch from "node-fetch"; import { simpleParser } from "mailparser"; const app = express(); -const PORT = 3334; -app.get("/", async (req, res) => { +const PORT = Number(process.env.PORT || 3334); +const SES_ENDPOINT = process.env.SES_VIEWER_ENDPOINT || "http://localhost:4566/_aws/ses"; +const FETCH_TIMEOUT_MS = Number(process.env.SES_VIEWER_FETCH_TIMEOUT_MS || 5000); +const DEFAULT_REFRESH_MS = Number(process.env.SES_VIEWER_REFRESH_MS || 10000); + +app.use((req, res, next) => { + res.set("Cache-Control", "no-store"); + next(); +}); + +app.get("/", (req, res) => { + res.type("html").send(renderHtml()); +}); + +app.get("/app.js", (req, res) => { + res.type("application/javascript").send(`(${clientApp.toString()})(${JSON.stringify(getClientConfig())});`); +}); + +app.get("/health", (req, res) => { + res.json({ + ok: true, + endpoint: SES_ENDPOINT, + port: PORT, + defaultRefreshMs: DEFAULT_REFRESH_MS + }); +}); + +app.get("/api/messages", async (req, res) => { try { - const response = await fetch("http://localhost:4566/_aws/ses"); - if (!response.ok) { - throw new Error("Network response was not ok"); - } - const data = await response.json(); - const messagesHtml = await parseMessages(data.messages); - res.send(renderHtml(messagesHtml)); + res.json(await loadMessages()); } catch (error) { console.error("Error fetching messages:", error); - res.status(500).send("Error fetching messages"); + res.status(502).json({ + error: "Unable to fetch messages from LocalStack SES", + details: error.message, + endpoint: SES_ENDPOINT + }); } }); -async function parseMessages(messages) { - const parsedMessages = await Promise.all( - messages.map(async (message, index) => { - try { - const parsed = await simpleParser(message.RawData); - return ` -
LocalStack SES
+A faster local inbox for generated emails with live refresh, manual refresh, search, and raw MIME inspection.