Conversation
Reviewer's GuideThis PR implements full support for POS product groups by extending types and schemas, enhancing the product form UI, integrating new hooks in the PosEdit flow, updating backend GraphQL mutations, fixing a TRPC method, and normalizing imports and formatting across modules. Sequence diagram for POS product group save flow in PosEditsequenceDiagram
participant User
participant PosEdit
participant usePosEditProductGroup
participant Backend
User->>PosEdit: Clicks Save on POS Edit
PosEdit->>usePosEditProductGroup: Calls productGroupSave(finalDataProductGroup)
usePosEditProductGroup->>Backend: GraphQL mutation productGroupsBulkInsert
Backend-->>usePosEditProductGroup: Returns updated product groups
usePosEditProductGroup-->>PosEdit: Refetch product groups
PosEdit-->>User: Shows toast 'POS edited successfully'
Class diagram for updated ProductGroup and ProductServiceSettings typesclassDiagram
class ProductGroup {
+string name
+string[] categoryIds
+string[] excludedCategoryIds
+string[] excludedProductIds
+string description
}
class ProductServiceSettings {
+ProductGroup[] productGroups
+string initialProductCategory
+string kioskExcludeCategories
+string kioskExcludeProducts
+boolean remainderConfigEnabled
+string excludeCategories
+boolean banFractions
}
ProductServiceSettings "1" o-- "*" ProductGroup
Class diagram for updated ProductFormValues and related schemasclassDiagram
class ProductFormValues {
+string[] productDetails
+CatProdMapping[] catProdMappings
+string[] initialCategoryIds
+string[] kioskExcludeCategoryIds
+string[] kioskExcludeProductIds
+string[] checkExcludeCategoryIds
+ProductGroup[] productGroups
}
class CatProdMapping {
+string _id
+string categoryId
+string productId
+string name
+string code
}
class ProductGroup {
+string name
+string[] categoryIds
+string[] excludedCategoryIds
+string[] excludedProductIds
+string description
+boolean isRequired
}
ProductFormValues "1" o-- "*" CatProdMapping
ProductFormValues "1" o-- "*" ProductGroup
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
WalkthroughThis pull request refactors POS product management by introducing product groups as a distinct entity. Backend procedures are updated to use mutations for data modifications, and frontend types, components, and hooks are restructured to support the new product group workflow with corresponding UI updates. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend as Frontend (POS Detail)
participant Mutation as Backend (posEdit Mutation)
participant ProductGroup as Backend (productGroupsBulkInsert)
participant Cache as Apollo Cache
User->>Frontend: Submit POS edit with product groups
Frontend->>Mutation: Call posEdit mutation
Mutation-->>Frontend: posEdit response
Frontend->>ProductGroup: Call productGroupSave (productGroupsBulkInsert)
ProductGroup-->>Frontend: Product groups saved, data.productGroupsBulkInsert returned
Frontend->>Cache: Update cache with fieldsToUpdate map
Cache-->>Frontend: Cache updated
Frontend->>Frontend: Show POS_EDITED toast
Frontend->>User: Success notification
sequenceDiagram
participant Worker as Worker (initWorker)
participant Input as Input Handler
participant TRPC as TRPC Method Router
participant Backend as Backend Procedure
Worker->>Input: Receive operation (lastAction, data)
Input->>Input: Build local input object
alt lastAction === 'crudData'
Input->>TRPC: Route to mutation
else
Input->>TRPC: Route to query
end
TRPC->>Backend: Call selected procedure
Backend-->>Worker: Return result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes The changes span multiple layers (backend procedures, worker logic, frontend types, components, and hooks) with diverse modifications including procedure type conversions, significant form refactoring, new type definitions, and product group workflow integration. While many individual changes are straightforward formatting or schema adjustments, the interconnected nature of product group introduction across backend and frontend, combined with form logic restructuring and new hook wiring, requires careful verification of type consistency, data flow, and UI/mutation coordination. Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- In sendPosclientMessage, the code references an undefined variable 'data'—it should use 'args.data' (or properly destructure) to prevent runtime failures.
- The 'POS edited' toast uses a 'destructive' variant which suggests an error; consider switching to a success or neutral variant for clarity.
- usePosEditProductGroup is invoked with an empty fields array, so cache.modify will never run; either pass the intended fields or simplify the cache update logic.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In sendPosclientMessage, the code references an undefined variable 'data'—it should use 'args.data' (or properly destructure) to prevent runtime failures.
- The 'POS edited' toast uses a 'destructive' variant which suggests an error; consider switching to a success or neutral variant for clarity.
- usePosEditProductGroup is invoked with an empty fields array, so cache.modify will never run; either pass the intended fields or simplify the cache update logic.
## Individual Comments
### Comment 1
<location> `frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx:47` </location>
<code_context>
useEffect(() => {
if (!posDetail) return;
-
+ console.log('productGroups', productGroups);
form.reset({
productDetails: posDetail.productDetails || [],
</code_context>
<issue_to_address>
**suggestion:** Remove console.log statement before merging.
Console.log statements should be removed from production code to avoid unnecessary log output and possible exposure of sensitive data.
```suggestion
```
</issue_to_address>
### Comment 2
<location> `backend/plugins/sales_api/src/initWorker.ts:66-74` </location>
<code_context>
const ret = await sendTRPCMessage({
pluginName: 'posclient',
method: lastAction === 'crudData' ? 'mutation' : 'query',
module: 'posclient',
action: lastAction,
input: { ...input },
defaultValue: {},
});
return ret;
</code_context>
<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/inline-immediately-returned-variable))
```suggestion
return await sendTRPCMessage({
pluginName: 'posclient',
method: lastAction === 'crudData' ? 'mutation' : 'query',
module: 'posclient',
action: lastAction,
input: { ...input },
defaultValue: {},
});
```
<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.
Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.
Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const ret = await sendTRPCMessage({ | ||
| pluginName: 'posclient', | ||
| method: 'query', | ||
| method: lastAction === 'crudData' ? 'mutation' : 'query', | ||
| module: 'posclient', | ||
| action: lastAction, | ||
| input: { data: { ...data, token: pos.token } }, | ||
| defaultValue: '', | ||
| input: { ...input }, | ||
| defaultValue: {}, | ||
| }); | ||
| return ret; |
There was a problem hiding this comment.
suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)
| const ret = await sendTRPCMessage({ | |
| pluginName: 'posclient', | |
| method: 'query', | |
| method: lastAction === 'crudData' ? 'mutation' : 'query', | |
| module: 'posclient', | |
| action: lastAction, | |
| input: { data: { ...data, token: pos.token } }, | |
| defaultValue: '', | |
| input: { ...input }, | |
| defaultValue: {}, | |
| }); | |
| return ret; | |
| return await sendTRPCMessage({ | |
| pluginName: 'posclient', | |
| method: lastAction === 'crudData' ? 'mutation' : 'query', | |
| module: 'posclient', | |
| action: lastAction, | |
| input: { ...input }, | |
| defaultValue: {}, | |
| }); | |
Explanation
Something that we often see in people's code is assigning to a result variableand then immediately returning it.
Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.
Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
|
There was a problem hiding this comment.
Caution
Changes requested ❌
Reviewed everything up to c859769 in 2 minutes and 28 seconds. Click for details.
- Reviewed
1081lines of code in10files - Skipped
0files when reviewing. - Skipped posting
13draft comments. View those below. - Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. backend/plugins/posclient_api/src/modules/posclient/trpc/posclient.ts:22
- Draft comment:
Refactored input handling: now usingconst data = inputinstead of destructuring{ data } = input. Verify that all callers provide the expected input shape. - Reason this comment was not posted:
Comment did not seem useful. Confidence is useful =0%<= threshold50%The comment is asking the PR author to verify that all callers provide the expected input shape, which is against the rules as it asks for verification. It does not provide a specific suggestion or point out a specific issue with the code.
2. backend/plugins/posclient_api/src/modules/posclient/trpc/posclient.ts:68
- Draft comment:
Changed 'crudData' procedure from query to mutation. Ensure that any client callers are updated to use mutation for this action. - Reason this comment was not posted:
Comment did not seem useful. Confidence is useful =0%<= threshold50%The comment is asking the author to ensure that client callers are updated, which is against the rules. It is not making a specific suggestion or pointing out a specific issue with the code.
3. backend/plugins/sales_api/src/initWorker.ts:64
- Draft comment:
The method selection in sendTRPCMessage now uses a ternary based on 'lastAction'. Verify that using 'mutation' for 'crudData' doesn’t impact other actions. - Reason this comment was not posted:
Confidence changes required:50%<= threshold50%None
4. backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.ts:80
- Draft comment:
In the groupsToRemove filter callback, return a boolean instead of 'el._id'. Use 'return index === -1' for clarity. - Reason this comment was not posted:
Comment was on unchanged code.
5. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts:18
- Draft comment:
Schema update: 'productDetailSchema' now uses arrays for excluded IDs and optional 'categoryIds'. Ensure UI and backend are updated to match this new shape. - Reason this comment was not posted:
Comment did not seem useful. Confidence is useful =0%<= threshold50%The comment is asking the author to ensure that the UI and backend are updated to match the new schema shape. This falls under the rule of not asking the author to ensure behavior is intended or tested. Therefore, this comment should be removed.
6. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts:25
- Draft comment:
In 'catProdMappingSchema', productIds changed to a single 'productId' with optional name/code. Confirm that downstream mapping logic is consistent with this schema. - Reason this comment was not posted:
Comment did not seem useful. Confidence is useful =0%<= threshold50%The comment is asking the author to confirm that the downstream mapping logic is consistent with the schema change. This falls under asking the author to ensure behavior is intended, which is not allowed.
7. frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx:214
- Draft comment:
Toast for POS edit uses variant 'destructive'. For a successful edit, consider using a 'success' variant. - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% While this appears to be a valid observation about incorrect toast styling, our rules explicitly state "Do NOT comment on any pure frontend UI or styling changes, only logic." Toast styling is purely a UI concern and doesn't affect functionality. The toast will still show the success message regardless of its visual style. The toast variant could potentially be considered part of the UX logic rather than pure styling, as it communicates the type of operation to the user. Maybe this is more than just cosmetic. Even if the toast variant has UX implications, it's still fundamentally a UI styling choice. The rules are clear about not commenting on UI/styling changes. Following our rules strictly, we should delete this comment as it relates to UI styling rather than logic.
8. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx:97
- Draft comment:
Typographical notice: The text "categories's" on this line appears to contain an error. If you mean the products belonging to multiple categories, consider changing it to "categories'". Additionally, "synched" is a less common variant; "synced" might be clearer. Finally, if 'pos' refers to a point of sale system, you might consider capitalizing it as "POS" for consistency. - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% While the comment is technically correct about the grammar and spelling, our rules state that we should only keep comments that require clear code changes. UI text and styling changes are explicitly called out as something we should NOT comment on. The rule states "Do NOT comment on any pure frontend UI or styling changes, only logic. Assume that if the author changed the UI, they did it correctly." The text issues could impact readability and professionalism of the UI. Poor grammar/spelling might confuse users. While readability is important, our rules are clear that UI text changes are out of scope. We must trust the author's judgment on UI-related decisions. Delete this comment as it only addresses UI text formatting which is explicitly out of scope for our review.
9. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx:122
- Draft comment:
Typographical note: The label text "CATEGORY included" may be unintentional. It’s inconsistent with the prior styling (e.g., "PRODUCT *") and might be meant to simply read "CATEGORY" (or be fully capitalized, e.g., "CATEGORY INCLUDED"). Please verify if this is the intended label. - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% The comment suggests changing the casing for consistency, but looking at the code, the current casing appears to be an intentional design choice. The UI has a mix of casing styles that seem purposeful. This appears to be a purely stylistic suggestion that doesn't impact functionality or clarity. I could be wrong about the intentionality of the casing styles. There might be a style guide I'm not aware of. Even if there is a style guide, this kind of minor UI text casing suggestion is not important enough to warrant a PR comment. The current text is clear and readable. Delete this comment as it's a minor stylistic suggestion about UI text that doesn't impact functionality or clarity.
10. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx:162
- Draft comment:
Typo: In the label 'Exclude PRODUCT`s', the backtick appears to be a mistake. Consider changing it to 'Exclude PRODUCTS' (or similar) for consistency. - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% While the comment is technically correct about the backtick being incorrect, this is an extremely minor UI text issue. The rules state not to comment on pure frontend UI changes and to assume the author made UI changes correctly. This falls into that category - it's just label text. The backtick could potentially cause confusion for users or make the UI look unprofessional. Maybe this isn't purely a UI issue since it affects usability? No, this is still fundamentally a UI text issue. The backtick vs apostrophe doesn't affect functionality, and the rules clearly state to trust the author on UI changes. Delete this comment as it violates the rule about not commenting on pure frontend UI changes.
11. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx:245
- Draft comment:
Typo found: The phrase "These categories's products" uses an unnecessary apostrophe. Consider changing it to "These categories' products". - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% While the grammar correction is technically correct, our rules state that we should not make purely informative comments that don't require code changes. This is a very minor text change in a UI string, and doesn't affect the functionality of the code. Additionally, we're told not to comment on pure frontend UI changes. The text is customer-facing UI text, so one could argue that proper grammar is important for professionalism. The fix is also very simple and clear. Even for customer-facing text, this is an extremely minor issue that doesn't impact understanding. Our rules explicitly state not to comment on UI changes and to only comment when there's clearly a code change required. Delete this comment as it's a minor UI text change that doesn't impact functionality, and our rules explicitly state not to comment on UI changes.
12. frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx:446
- Draft comment:
There appears to be a stray comment marker (*/) at the end of the line. It looks like leftover code from commented-out logic. Please review and remove if it's not needed. - Reason this comment was not posted:
Comment looked like it was already resolved.
13. frontend/plugins/sales_ui/src/modules/pos/create-pos/types/index.ts:59
- Draft comment:
Typo: The property name "merchantin" appears to be a typographical error. Consider renaming it to "merchantId" if that is the intended meaning. - Reason this comment was not posted:
Comment was on unchanged code.
Workflow ID: wflow_Y4WIALhNflHpEeu4
You can customize by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.
| useEffect(() => { | ||
| if (!posDetail) return; | ||
|
|
||
| console.log('productGroups', productGroups); |
There was a problem hiding this comment.
Remove or disable debug logging (console.log) in production code.
| console.log('productGroups', productGroups); |
| render={({ field }) => ( | ||
| <Form.Item> | ||
| <Form.Label className="text-sm text-[#A1A1AA] uppercase font-semibold"> | ||
| Exclude PRODUCT`s |
There was a problem hiding this comment.
Label text "Exclude PRODUCTs"contains an unexpected backtick; consider using a consistent label like"Exclude PRODUCTS"`.
| Exclude PRODUCT`s | |
| Exclude PRODUCTS |
| const variables = operationVariables?.variables || {}; | ||
| const fieldsToUpdate: Record<string, () => any> = {}; | ||
|
|
||
| fields.forEach((field) => { |
There was a problem hiding this comment.
Consider adding error handling in the update callback of the mutation, and validate the fields parameter before use.
| posId: posDetail._id, | ||
| groups: [ | ||
| ...productValues.productGroups.map((item) => ({ | ||
| _id: `temporaryId.${Math.random()}`, |
There was a problem hiding this comment.
Temporary IDs are generated using Math.random(), which may lead to collisions. Consider using a more robust unique ID generator.
| _id: `temporaryId.${Math.random()}`, | |
| _id: `temporaryId.${crypto.randomUUID()}`, |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
backend/plugins/posclient_api/src/modules/posclient/trpc/posclient.ts (1)
71-99: Good: mutate for CRUD; add validation and consistent return
- Mutation is correct. However, input is
z.any()andtokenis used without guarantees in 'productGroups'/'slots'.- Return value is undefined; other endpoints return
{ status: 'success' }.Apply minimal guards and a consistent response:
+import { TRPCError } from '@trpc/server'; ... - crudData: t.procedure.input(z.any()).mutation(async ({ ctx, input }) => { + crudData: t.procedure.input(z.object({ + type: z.enum(['product','productCategory','user','productGroups','slots']), + token: z.string().optional(), + }).passthrough()).mutation(async ({ ctx, input }) => { const { models, subdomain } = ctx; const { token, type } = input; @@ - if (type) { + if (type) { switch (type) { @@ case 'productGroups': - const { productGroups = [] } = input; + const { productGroups = [] } = input as any; + if (!token) throw new TRPCError({ code: 'BAD_REQUEST', message: 'token is required for productGroups' }); await preImportProducts(models, token, productGroups); await importProducts(subdomain, models, token, productGroups); break; case 'slots': - const { slots = [] } = input; + const { slots = [] } = input as any; + if (!token) throw new TRPCError({ code: 'BAD_REQUEST', message: 'token is required for slots' }); await importSlots(models, slots, token); break; @@ - } - }), + } + return { status: 'success' }; + }),
- Consider converting other write operations (e.g.,
updateSynced,erxes_posclient_to_pos_api(_remove),paymentCallbackClient) to mutations in a follow-up for semantic consistency.backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.ts (1)
80-88: Bug: deleteMany uses documents instead of IDs
groupsToRemoveholds docs (fromdbGroups.filter(...)), but$inexpects IDs. Also the filter callback should return boolean, not an_idvalue.- const groupsToRemove = dbGroups.filter((el) => { - const index = groupsToUpdate.findIndex((g) => g._id === el._id); - if (index === -1) { - return el._id; - } - }); - if (groupsToRemove.length > 0) { - await models.ProductGroups.deleteMany({ _id: { $in: groupsToRemove } }); - } + const groupsToRemoveIds = dbGroups + .filter((el) => groupsToUpdate.findIndex((g) => g._id === el._id) === -1) + .map((el) => el._id); + if (groupsToRemoveIds.length > 0) { + await models.ProductGroups.deleteMany({ _id: { $in: groupsToRemoveIds } }); + }frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx (1)
118-151: Based on the verification results, I have confirmed that the original review comment is accurate. The schema clearly defines both fields asz.array(z.string()), yet the UI implementation only allows selecting a single value at a time by wrapping it in an array. This is a legitimate type-to-UI mismatch that requires clarification and resolution.Type-to-UI mismatch: categoryIds/excludedCategoryIds
Schema defines both fields as
z.array(z.string()), but the UI component only allows single-select mode (taking only the first element withfield.value?.[0]and wrapping selections as[value]). Clarify the intended behavior:
- If multiple categories should be allowed, update SelectCategory to multi-select
- If only a single category is needed, narrow the schema types from
z.array(z.string())toz.string()frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx (1)
184-201: Add product fields to cache update list to prevent stale UIThe mutation sends product fields (
catProdMappings,initialCategoryIds,kioskExcludeCategoryIds,kioskExcludeProductIds,checkExcludeCategoryIds) to the backend but excludes them from the Apollo cache update. Include them in the fields array to sync the cache after mutation.[ 'name', 'description', 'allowTypes', 'scopeBrandIds', 'branchId', 'adminTeamMember', 'adminPrintTempBill', 'adminDirectSales', 'adminDirectDiscountLimit', 'cashierTeamMember', 'cashierPrintTempBill', 'cashierDirectSales', 'cashierDirectDiscountLimit', 'adminIds', 'cashierIds', 'permissionConfig', + 'catProdMappings', + 'initialCategoryIds', + 'kioskExcludeCategoryIds', + 'kioskExcludeProductIds', + 'checkExcludeCategoryIds', ],
🧹 Nitpick comments (8)
backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.ts (1)
122-129: Slots bulk update looks good; upsert optional
upsert: truefor updates is not required since inserts are handled below; leaving it could mask accidental missing IDs.- upsert: true, + upsert: false,Also applies to: 132-137
frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts (1)
26-31: Minor schema tweaks look fine
- Optional
_idfor mappings/payment types.- Optional
branchId/departmentId.assignedUserIdsdefaulted to[].- combineFormData includes
deliveryConfig.Consider
.strict()on key schemas to catch typos during form submission.Also applies to: 34-34, 87-87, 112-114, 162-163, 194-198
frontend/plugins/sales_ui/src/modules/pos/create-pos/hooks/usePosCreateForm.tsx (1)
148-153: Type-safe updateFormStepDataAvoid any; make it generic so data matches the selected step’s schema.
- const updateFormStepData = (step: keyof FormStepData, data: any) => { - setFormStepData((prev) => ({ - ...prev, - [step]: data, - })); - }; + function updateFormStepData<K extends keyof FormStepData>( + step: K, + data: NonNullable<FormStepData[K]> + ) { + setFormStepData(prev => ({ ...prev, [step]: data })); + }frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx (5)
31-40: Default productGroups in formInitialize productGroups to [] to avoid uncontrolled-to-controlled edge cases when watching the field.
const internalForm = useForm<ProductFormValues>({ defaultValues: { productDetails: [], catProdMappings: [], initialCategoryIds: [], kioskExcludeCategoryIds: [], kioskExcludeProductIds: [], checkExcludeCategoryIds: [], + productGroups: [], }, });
47-56: Remove debug logDrop console.log in production code.
- console.log('productGroups', productGroups);
96-99: Copy tweaksMinor grammar for clarity.
- <p className="text-sm text-gray-500"> - These selected categories's products will be synched in this pos. - </p> + <p className="text-sm text-gray-500"> + Products from the selected categories will sync to this POS. + </p>
161-173: Typo in labelUse “Products” without a backtick.
- Exclude PRODUCT`s + Exclude products
112-156: Array fields: prefer useFieldArrayManaging arrays via watch/setValue works but loses some RHF benefits (dirty tracking, stable keys). useFieldArray is the idiomatic approach for productGroups and catProdMappings.
I can provide a focused diff using useFieldArray for these two sections.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
backend/plugins/posclient_api/src/modules/posclient/trpc/posclient.ts(2 hunks)backend/plugins/sales_api/src/initWorker.ts(1 hunks)backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.ts(7 hunks)frontend/plugins/sales_ui/src/modules/pos/constants/index.tsx(1 hunks)frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts(6 hunks)frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx(14 hunks)frontend/plugins/sales_ui/src/modules/pos/create-pos/hooks/usePosCreateForm.tsx(4 hunks)frontend/plugins/sales_ui/src/modules/pos/create-pos/types/index.ts(1 hunks)frontend/plugins/sales_ui/src/modules/pos/hooks/usePosEditProductGroup.tsx(1 hunks)frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx(6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursorrules)
**/*.{ts,tsx}: Write concise, technical TypeScript code
Use functional and declarative programming patterns; avoid classes
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Avoid console logs
Always use absolute paths for imports
Use TypeScript for all code (no .js/.jsx)
Prefer interfaces over types for object shapes
Avoid enums; use maps instead
Use the function keyword for pure functions
Avoid unnecessary curly braces in simple conditionals; use concise syntax
Files:
backend/plugins/posclient_api/src/modules/posclient/trpc/posclient.tsfrontend/plugins/sales_ui/src/modules/pos/hooks/usePosEditProductGroup.tsxbackend/plugins/sales_api/src/initWorker.tsfrontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsxfrontend/plugins/sales_ui/src/modules/pos/constants/index.tsxfrontend/plugins/sales_ui/src/modules/pos/create-pos/hooks/usePosCreateForm.tsxfrontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsxbackend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.tsfrontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.tsfrontend/plugins/sales_ui/src/modules/pos/create-pos/types/index.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursorrules)
**/*.tsx: Use declarative JSX
Use Shadcn UI, Radix UI, and Tailwind for components and styling
Implement responsive, mobile-first design with Tailwind CSS
Minimize useEffect and setState usage
Wrap client components in React.Suspense with a fallback
Use dynamic loading for non-critical components
Include explicit width and height (or sizes) when rendering images
Implement lazy loading for images
Limit 'use client': favor server components and Next.js SSR
Use 'use client' only for Web API access in small components
Do not use 'use client' components for data fetching or state management
Files:
frontend/plugins/sales_ui/src/modules/pos/hooks/usePosEditProductGroup.tsxfrontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsxfrontend/plugins/sales_ui/src/modules/pos/constants/index.tsxfrontend/plugins/sales_ui/src/modules/pos/create-pos/hooks/usePosCreateForm.tsxfrontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx
🧬 Code graph analysis (4)
frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx (2)
frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts (1)
ProductFormValues(167-167)frontend/plugins/sales_ui/src/modules/pos/create-pos/types/index.ts (1)
ProductGroup(26-32)
frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx (4)
frontend/plugins/sales_ui/src/modules/pos/hooks/usePosEditProductGroup.tsx (1)
usePosEditProductGroup(4-36)frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts (1)
ProductFormValues(167-167)frontend/plugins/sales_ui/src/modules/pos/constants/index.tsx (1)
TOAST_MESSAGES(148-155)frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsx (1)
ProductForm(20-586)
backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.ts (1)
backend/plugins/posclient_api/src/modules/posclient/graphql/resolvers/customResolvers/posConfig.ts (1)
slots(5-7)
frontend/plugins/sales_ui/src/modules/pos/create-pos/types/index.ts (1)
frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts (8)
BasicInfoFormValues(165-165)PermissionFormValues(166-166)ProductFormValues(167-167)PaymentFormValues(168-168)UiConfigFormValues(169-169)DeliveryConfigFormValues(170-170)FinanceConfigFormValues(171-171)FormStepData(174-183)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: posclient_api-ci
- GitHub Check: sales_api-ci
- GitHub Check: Sourcery review
🔇 Additional comments (4)
frontend/plugins/sales_ui/src/modules/pos/constants/index.tsx (1)
148-155: LGTM: added POS_EDITED toastConsistent naming and usage with existing messages.
backend/plugins/sales_api/src/initWorker.ts (1)
64-75: Action→method mapping: use Set for maintainability; verify flattened input requirementThe Set-based refactoring is valid for future extensibility. However, the verification request about flattened input cannot be confirmed—configs.manage still passes
data: { type: 'pos', ...configData }and is properly spread into input with token. Clarify whether this structure needs to change or if the original code is already correct.backend/plugins/posclient_api/src/modules/posclient/trpc/posclient.ts (1)
23-29: Input shape is correctly flattened by the message handler; no breaking change existsYour concern assumes callers send wrapped
{ data }, but investigation shows the only caller (syncPosToClientin sales_api) usessendPosclientMessage, which flattens the data before reaching the TRPC endpoint. The input received at line 23 is already{ type, pos, adminUsers, cashiers, token }, which matches whatreceivePosConfigdestructures at line 436. The assignmentconst data = input;is correct.Likely an incorrect or invalid review comment.
frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx (1)
202-213: Don’t assign random names to groupsNames like
temporaryId.${Math.random()}will persist junk data. Use existing item.name or omit if API allows; otherwise derive a deterministic label.- groups: [ - ...productValues.productGroups.map((item) => ({ - _id: `temporaryId.${Math.random()}`, - name: `temporaryId.${Math.random()}`, - categoryIds: item.categoryIds, - excludedCategoryIds: item.excludedCategoryIds, - excludedProductIds: item.excludedProductIds, - })), - ], + groups: productValues.productGroups.map((item, i) => ({ + ...(item._id ? { _id: item._id } : {}), + ...(item.name ? { name: item.name } : {}), + categoryIds: item.categoryIds, + excludedCategoryIds: item.excludedCategoryIds, + excludedProductIds: item.excludedProductIds, + })),
| params.onServer = true; // default pos config | ||
| const pos = await models.Pos.posAdd(user, params); |
There was a problem hiding this comment.
🧩 Analysis chain
Forcing onServer=true may override explicit false
Setting params.onServer = true and doc.onServer = true unconditionally changes behavior versus env‑gated logic. Preserve explicit false from clients and default only when undefined.
- params.onServer = true; // default pos config
+ if (typeof params.onServer === 'undefined') {
+ params.onServer = true; // default pos config
+ }
@@
- doc.onServer = true; // default pos config
+ if (typeof doc.onServer === 'undefined') {
+ doc.onServer = true; // default pos config
+ }Confirm no flows rely on onServer: false (e.g., remote-only POS).
Also applies to: 42-43
🏁 Script executed:
#!/bin/bash
# Inspect the target file to understand context
cat backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.tsLength of output: 4105
🏁 Script executed:
#!/bin/bash
# Search for onServer usage throughout the codebase
rg -nP --type=ts --type=tsx 'onServer' -B2 -A2Length of output: 27
🏁 Script executed:
#!/bin/bash
# Search specifically for onServer being set to false
rg -nP --type=ts --type=tsx 'onServer\s*[=:]\s*false' -B3 -A3Length of output: 27
🏁 Script executed:
#!/bin/bash
# Search for POS model and posAdd method to understand the flow
rg -nP 'posAdd' -B3 -A5Length of output: 14026
🏁 Script executed:
#!/bin/bash
# Retry search for onServer usage with correct rg syntax (no tsx type)
rg -nP 'onServer' -B2 -A2 --type tsLength of output: 17995
🏁 Script executed:
#!/bin/bash
# Search for any usage of onServer: false or onServer false across entire codebase
rg -nP 'onServer.*false|false.*onServer' -B2 -A2Length of output: 521
🏁 Script executed:
#!/bin/bash
# Check if there are any type definitions for IPos that document onServer
fd -e ts -e js | xargs grep -l 'interface IPos\|type IPos' | head -5Length of output: 364
Unconditional onServer=true override breaks remote-only POS flows
The code unconditionally sets onServer = true after the environment check, overriding any explicit false from clients. This breaks routing logic: initWorker.ts checks !pos.onServer to queue actions to posclient instead of handling server-side (lines 47-49). The suggested fix is correct—check for undefined before defaulting.
Lines 21-22 and 42-43:
- params.onServer = true; // default pos config
+ if (typeof params.onServer === 'undefined') {
+ params.onServer = true; // default pos config
+ }Apply the same pattern to doc.onServer at line 42-43.
🤖 Prompt for AI Agents
In backend/plugins/sales_api/src/modules/pos/graphql/resolvers/mutations/pos.ts
around lines 21-22 and 42-43, the code unconditionally sets params.onServer =
true (and similarly doc.onServer) which overrides explicit false from clients
and breaks remote-only POS flows; change both spots to only set the default when
the value is undefined (e.g., if (params.onServer === undefined) params.onServer
= true) and apply the same undefined-check pattern for doc.onServer so existing
false values passed by clients are preserved.
| export const productSchema = z.object({ | ||
| productDetails: z.array(productDetailSchema).default([]), | ||
| productDetails: z.array(z.string()).default([]), | ||
| catProdMappings: z.array(catProdMappingSchema).default([]), | ||
| initialCategoryIds: z.array(z.string()).default([]), | ||
| kioskExcludeCategoryIds: z.array(z.string()).default([]), | ||
| kioskExcludeProductIds: z.array(z.string()).default([]), | ||
| checkExcludeCategoryIds: z.array(z.string()).default([]), | ||
| productGroups: z.array(productDetailSchema).default([]), | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
productGroups schema likely incomplete vs UI needs
productGroups uses productDetailSchema (no name/description). If UI/backends expect group metadata, define a dedicated schema.
+const productGroupSchema = z.object({
+ _id: z.string().optional(),
+ name: z.string().min(1, 'Group name is required'),
+ description: z.string().default(''),
+ categoryIds: z.array(z.string()).default([]),
+ excludedCategoryIds: z.array(z.string()).default([]),
+ excludedProductIds: z.array(z.string()).default([]),
+ isRequired: z.boolean().default(false),
+});
...
-export const productSchema = z.object({
- productDetails: z.array(z.string()).default([]),
+export const productSchema = z.object({
+ productDetails: z.array(z.string()).default([]),
@@
- productGroups: z.array(productDetailSchema).default([]),
+ productGroups: z.array(productGroupSchema).default([]),Verify consumers of productGroups expect objects with name etc. and update types accordingly.
I'll verify this review comment by examining the codebase to understand the schema definitions and how productGroups is used.
🏁 Script executed:
#!/bin/bash
# Find the formSchema.ts file and examine schema definitions
fd -type f -name "formSchema.ts" | head -5Length of output: 170
🏁 Script executed:
#!/bin/bash
# Search for productDetailSchema definition
rg -n "productDetailSchema\s*=" --type tsLength of output: 119
🏁 Script executed:
#!/bin/bash
# Search for productGroups usage in the codebase
rg -n "productGroups" --type ts -C 3Length of output: 34443
🏁 Script executed:
#!/bin/bash
# Search for productGroupSchema if it already exists
rg -n "productGroupSchema\s*=" --type tsLength of output: 115
🏁 Script executed:
#!/bin/bash
# Search for productSchema usage to understand consumers
rg -n "productSchema" --type ts -C 2 | head -50Length of output: 3929
🏁 Script executed:
#!/bin/bash
# Look for any group-related schemas or types
rg -n "group.*Schema|GroupSchema" --type ts -iLength of output: 5107
🏁 Script executed:
#!/bin/bash
# Check for name and description fields being used with productGroups
rg -n "\.name|\.description" --type ts | grep -i group | head -20Length of output: 2523
🏁 Script executed:
#!/bin/bash
# Examine the specific file and surrounding context
cat frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts 2>/dev/null || echo "File not found in expected location"Length of output: 6875
🏁 Script executed:
#!/bin/bash
# Search more broadly for formSchema files
fd -type f -name "*formSchema*" --type fLength of output: 170
🏁 Script executed:
#!/bin/bash
# Search for product group related code patterns
rg -n "productGroup|product.*group" --type ts -i | head -30Length of output: 3791
🏁 Script executed:
#!/bin/bash
# Check for any imports or references that might indicate schema structure
rg -n "import.*productDetailSchema|import.*productSchema" --type ts -C 1Length of output: 690
🏁 Script executed:
#!/bin/bash
# Search for object patterns with name and description to understand group expectations
rg -nP "name\s*:\s*z\.string.*description\s*:\s*z\.string" --type ts -A 2 -B 2Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for any UI components using productGroups to understand expectations
rg -n "productGroups\?" --type tsx -C 5Length of output: 27
🏁 Script executed:
#!/bin/bash
# Find all schema definitions in the formSchema file if it exists
fd -type f -name "formSchema.ts" -x cat {}Length of output: 170
🏁 Script executed:
#!/bin/bash
# Search for type or interface definitions related to product groups
rg -n "interface.*[Gg]roup|type.*[Gg]roup" --type ts | grep -i product | head -20Length of output: 1679
🏁 Script executed:
#!/bin/bash
# Check API response or backend schema references
rg -n "Group.*name.*description|group.*metadata" --type ts -i -C 2 | head -30Length of output: 1085
🏁 Script executed:
#!/bin/bash
# Look at all z.object definitions to understand schema patterns
rg -n "const.*Schema\s*=" --type ts | grep -E "(product|group)" | head -20Length of output: 2150
🏁 Script executed:
#!/bin/bash
# Search in TypeScript files more broadly for schema files
find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "productSchema\|productDetailSchema" 2>/dev/null | head -10Length of output: 510
🏁 Script executed:
#!/bin/bash
# Get complete view of schemas in the directory
rg -n "export const.*Schema" frontend/plugins/sales_ui/src/modules/pos/create-pos/components/ --type ts -A 5 2>/dev/null | head -100Length of output: 6668
🏁 Script executed:
#!/bin/bash
# Search for the actual file more carefully with broader path search
find . -path "*sales_ui*" -name "*formSchema*" -type f 2>/dev/nullLength of output: 149
🏁 Script executed:
#!/bin/bash
# Try alternative approach - search all TypeScript files for productSchema definition
rg -l "export const productSchema" --type tsLength of output: 221
🏁 Script executed:
#!/bin/bash
# Get content of the specific file from any location
rg -A 30 "export const productSchema" --type ts -B 5Length of output: 11535
🏁 Script executed:
#!/bin/bash
# List files in the likely directory
ls -la frontend/plugins/sales_ui/src/modules/pos/create-pos/components/ 2>/dev/null || echo "Path not found - searching alternatives"Length of output: 817
🏁 Script executed:
#!/bin/bash
# Search for any POS-related formSchema files
find . -type d -name "create-pos" 2>/dev/null -exec find {} -name "*Schema*" \;Length of output: 79
🏁 Script executed:
#!/bin/bash
# Broad search for all schema files that mention products
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "productSchema\|productDetailSchema" {} \; 2>/dev/null | head -15Length of output: 510
🏁 Script executed:
#!/bin/bash
# Look for complete schema by searching more lines
rg -A 15 "export const productSchema = z.object" --type tsLength of output: 1887
🏁 Script executed:
#!/bin/bash
# Find where productGroups is actually used
rg "productGroups" --type ts --type tsx -B 5 -A 5 | head -100Length of output: 27
🏁 Script executed:
#!/bin/bash
# Check what fields are expected in group items
rg -n "\._id|\['_id'\]|\.name|\['name'\]" --type ts | grep -i group -A 2 -B 2 | head -40Length of output: 4959
🏁 Script executed:
#!/bin/bash
# Directory structure investigation
find . -type d -name "pos" -o -type d -name "create-pos" 2>/dev/nullLength of output: 190
🏁 Script executed:
#!/bin/bash
# Final comprehensive search for schema file content
rg "productDetailSchema\|productGroupSchema" --type ts -B 10 -A 10 | head -150Length of output: 0
🏁 Script executed:
#!/bin/bash
# Search for group object creation or typing patterns
rg "name.*description.*category|category.*name" --type ts -i -C 3 | grep -i group -A 3 -B 3 | head -50Length of output: 0
🏁 Script executed:
#!/bin/bash
# Find ProductGroup interface definition in frontend
rg -n "export interface ProductGroup" --type ts -A 10Length of output: 1057
🏁 Script executed:
#!/bin/bash
# Find IProductGroup interface definition in backend
rg -n "export interface IProductGroup" --type ts -A 15Length of output: 6851
🏁 Script executed:
#!/bin/bash
# Check the backend productGroupSchema definition
rg -n "export const productGroupSchema" --type ts -A 20Length of output: 1959
🏁 Script executed:
#!/bin/bash
# Check GraphQL ProductGroups type definition
rg -n "type ProductGroups|type ProductGroups {" --type ts -A 15Length of output: 141
🏁 Script executed:
#!/bin/bash
# See what GroupInput expects in the mutation
rg -n "GroupInput" --type ts -A 10 -B 2Length of output: 6072
🏁 Script executed:
#!/bin/bash
# Check backend mutation resolver for productGroupsBulkInsert
rg -n "productGroupsBulkInsert" --type ts -A 20 | head -80Length of output: 6024
🏁 Script executed:
#!/bin/bash
# Look for group creation/editing in backend models
rg -n "groupsAdd|groupsEdit" --type ts -A 10 -B 2Length of output: 4576
🏁 Script executed:
#!/bin/bash
# Check how groups are persisted in backend
rg -n "ProductGroups.create|ProductGroup.create" --type ts -A 5 -B 2Length of output: 709
🏁 Script executed:
#!/bin/bash
# Verify what posDetail stores for productGroups
rg -n "posDetail.*productGroups\|productGroups.*posDetail" --type ts -C 3 | head -50Length of output: 0
🏁 Script executed:
#!/bin/bash
# Check if there's form validation or TypeScript type for productGroups
rg -n "type ProductFormValues" --type ts -A 20Length of output: 3022
🏁 Script executed:
#!/bin/bash
# Look at the GraphQL schema query return type for productGroups
rg -n "query productGroups" --type ts -A 15Length of output: 1326
🏁 Script executed:
#!/bin/bash
# Check if there's any group name/description field being used in the UI
rg -n "productGroups\[.*\]\.name|productGroups.*\.description" --type ts -C 2Length of output: 0
Schema mismatch confirmed; suggested fix needs revision
The review comment correctly identifies a critical schema mismatch. However, the proposed schema fix has issues:
Verified Problems:
- Frontend's
productDetailSchema(used forproductGroups) lacksnameanddescriptionfields - Backend's
IProductGroupinterface requiresname(string) anddescription(string) as mandatory fields - GraphQL
GroupInputand theproductGroupsquery returnname,description,categoryIds,excludedCategoryIds, andexcludedProductIds - Frontend form renders only
categoryIds,excludedCategoryIds,excludedProductIds, andisRequired—no fields for group name/description - Data transformation generates dummy names (
temporaryId.${Math.random()}) instead of capturing user input
Issues with the Proposed Diff:
- Includes
isRequiredfield (belongs toproductDetailSchema, not product group metadata) - Missing
posId(backend requires it) - Suggested schema structure mixes concerns
Correct approach:
Define a separate productGroupSchema with fields: _id, name, description, categoryIds, excludedCategoryIds, excludedProductIds (without isRequired). Update the UI form to include name and description inputs, and remove the temporary name workaround.
|
|
||
| export const productSchema = z.object({ | ||
| productDetails: z.array(productDetailSchema).default([]), | ||
| productDetails: z.array(z.string()).default([]), |
There was a problem hiding this comment.
🧩 Analysis chain
Breaking change: productDetails now string[]
All consumers that previously used objects will break. Confirm UI/components and API mappers are updated.
🏁 Script executed:
#!/bin/bash
# Inspect usages of productDetails
rg -nP -C3 --type=ts --type=tsx '\bproductDetails\b'Length of output: 27
I'll correct the search strategy and gather information about the productDetails changes.
---
🏁 Script executed:
```shell
#!/bin/bash
# View the formSchema.ts file around line 133
cat frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts
Length of output: 6875
🏁 Script executed:
#!/bin/bash
# Search for productDetails usages - simplified rg syntax
rg -nC3 'productDetails' --type=tsLength of output: 16289
🏁 Script executed:
#!/bin/bash
# Broader search for productDetails across all file types
rg -nC3 'productDetails'Length of output: 16289
Update productSchema.productDetails type or add mapping layer in consumers
Line 133 changes productSchema.productDetails to z.array(z.string()), but consumers receive ProductDetail[] objects:
- product.tsx:49 resets form with
posDetail.productDetails(objects from IPos.ts:34) - usePosAdd.tsx:163 already maps objects (
pd.productId) for API submission - Type mismatch: ProductFormValues now declares
string[]while receiving objects
Update either:
- Keep productSchema as
z.array(productDetailSchema)to match actual data, or - Add explicit mapping in product.tsx and pos-detail components to transform ProductDetail[] to string[] before form reset
Verify type consistency across product.tsx, pos-detail.tsx, and usePosAdd.tsx.
🤖 Prompt for AI Agents
In frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.ts
around line 133, productDetails was changed to z.array(z.string()) but the app
actually passes ProductDetail[] objects to the form; update either the schema
back to z.array(productDetailSchema) to match consumers or update all form
consumers (product.tsx and pos-detail.tsx) to map ProductDetail[] -> string[]
(extract productId) before calling form.reset; ensure usePosAdd.tsx mapping
remains correct and types for ProductFormValues reflect string[] if you choose
mapping, or ProductDetail[] if you keep the richer schema so all three files and
IPos.ts stay consistent.
| export interface ProductGroup { | ||
| name: string; | ||
| description?: string; | ||
| categoryIds?: string[]; | ||
| excludedCategoryIds?: string[]; | ||
| excludedProductIds?: string[]; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
Align ProductGroup type with UI usage
UI binds productGroups.[i].isRequired, but the interface lacks it. Add an optional isRequired or remove the field from UI to keep types consistent.
export interface ProductGroup {
name: string;
description?: string;
categoryIds?: string[];
excludedCategoryIds?: string[];
excludedProductIds?: string[];
+ isRequired?: boolean;
}🏁 Script executed:
#!/bin/bash
# Double-check for any existing declarations of isRequired on ProductGroup
rg -n -C2 -P 'ProductGroup.*isRequired' frontendLength of output: 0
🏁 Script executed:
#!/bin/bash
# Find where ProductGroup is used in the UI
rg -n -C3 'ProductGroup' frontend/plugins/sales_ui/src --type=ts --type=tsx | head -50Length of output: 27
🏁 Script executed:
#!/bin/bash
# Search for isRequired in the sales_ui plugin
rg -n 'isRequired' frontend/plugins/sales_ui/src --type=ts --type=tsxLength of output: 27
🏁 Script executed:
#!/bin/bash
# Find the component/file that uses productGroups and check for binding
rg -n -C4 'productGroups\s*\[' frontend/plugins/sales_ui/src --type=ts --type=tsxLength of output: 27
🏁 Script executed:
#!/bin/bash
# Find where ProductGroup is used in the UI (without type flags)
rg -n -C3 'ProductGroup' frontend/plugins/sales_ui/srcLength of output: 14880
🏁 Script executed:
#!/bin/bash
# Search for isRequired in the sales_ui plugin (without type flags)
rg -n 'isRequired' frontend/plugins/sales_ui/srcLength of output: 502
🏁 Script executed:
#!/bin/bash
# Find the component/file that uses productGroups and check for binding
rg -n -C4 'productGroups\s*\[' frontend/plugins/sales_ui/srcLength of output: 0
🏁 Script executed:
#!/bin/bash
# Get context around formSchema.ts line 22 to see if it's ProductGroup schema
rg -n -B5 -A5 'isRequired.*z\.boolean' frontend/plugins/sales_ui/src/modules/pos/create-pos/components/formSchema.tsLength of output: 373
🏁 Script executed:
#!/bin/bash
# Get context around the productGroups.${index}.isRequired binding in product.tsx
rg -n -B3 -A3 'productGroups.*isRequired' frontend/plugins/sales_ui/src/modules/pos/create-pos/components/product/product.tsxLength of output: 325
Add isRequired field to ProductGroup interface
The UI form binds to productGroups.[i].isRequired (line 182 in product.tsx) and the form schema validates it, but the ProductGroup interface lacks this field. Add the optional field to align types with runtime expectations.
export interface ProductGroup {
name: string;
description?: string;
categoryIds?: string[];
excludedCategoryIds?: string[];
excludedProductIds?: string[];
+ isRequired?: boolean;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export interface ProductGroup { | |
| name: string; | |
| description?: string; | |
| categoryIds?: string[]; | |
| excludedCategoryIds?: string[]; | |
| excludedProductIds?: string[]; | |
| } | |
| export interface ProductGroup { | |
| name: string; | |
| description?: string; | |
| categoryIds?: string[]; | |
| excludedCategoryIds?: string[]; | |
| excludedProductIds?: string[]; | |
| isRequired?: boolean; | |
| } |
🤖 Prompt for AI Agents
In frontend/plugins/sales_ui/src/modules/pos/create-pos/types/index.ts around
lines 26 to 32, the ProductGroup interface is missing the isRequired field
referenced by the UI form; add an optional property isRequired?: boolean to the
interface so the type matches runtime/form schema expectations.
| return saveProductGroups({ | ||
| ...operationVariables, | ||
| variables, | ||
| update: (cache, { data }) => { | ||
| const productGroupsBulkInsert = data?.productGroupsBulkInsert; | ||
| if (!productGroupsBulkInsert) return; | ||
|
|
||
| cache.modify({ | ||
| id: cache.identify(productGroupsBulkInsert), | ||
| fields: fieldsToUpdate, | ||
| }); | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
Guard cache.modify target and make update optional
If cache.identify returns undefined (array/unnormalized object), cache.modify will hit ROOT_QUERY. Also skip update when fields is empty (as in current caller).
- return saveProductGroups({
- ...operationVariables,
- variables,
- update: (cache, { data }) => {
- const productGroupsBulkInsert = data?.productGroupsBulkInsert;
- if (!productGroupsBulkInsert) return;
-
- cache.modify({
- id: cache.identify(productGroupsBulkInsert),
- fields: fieldsToUpdate,
- });
- },
- });
+ return saveProductGroups({
+ ...operationVariables,
+ variables,
+ update: fields.length
+ ? (cache, { data }) => {
+ const entity = data?.productGroupsBulkInsert;
+ if (!entity) return;
+ const entityId = cache.identify(entity);
+ if (!entityId) return;
+ cache.modify({ id: entityId, fields: fieldsToUpdate });
+ }
+ : undefined,
+ });Run to confirm the mutation field name and normalized key:
🏁 Script executed:
#!/bin/bash
# Find mutation and return type for product group save
rg -n -C3 -P '(mutation\s+saveProductGroups|productGroupsBulkInsert)' --type=ts --type=tsx --type=graphql
# Look for GraphQL type with key fields (__typename/id/_id) that identify productGroupsBulkInsert
rg -n -C2 -P '(type|interface)\s+.*ProductGroup|productGroupsBulkInsert|@key' --type=graphql --type=ts --type=tsxLength of output: 59
🏁 Script executed:
#!/bin/bash
# Search without file type restrictions for the mutation and related types
rg -n -C3 'saveProductGroups|productGroupsBulkInsert' --multilineLength of output: 5211
🏁 Script executed:
#!/bin/bash
# Check the target hook file directly
cat "frontend/plugins/sales_ui/src/modules/pos/hooks/usePosEditProductGroup.tsx"Length of output: 1005
🏁 Script executed:
#!/bin/bash
# Search for GraphQL files with different extensions
fd -e 'gql' -e 'graphql' -e 'ts' -e 'tsx' | head -20Length of output: 1195
🏁 Script executed:
#!/bin/bash
# Look for GraphQL schema or mutation definitions in common locations
find . -type f \( -name "*.graphql" -o -name "*.gql" -o -name "*schema*" -o -name "*mutation*" \) 2>/dev/null | grep -i "product\|group" | head -20Length of output: 90
🏁 Script executed:
#!/bin/bash
# Search for fieldsToUpdate definition and usage
rg -n 'fieldsToUpdate' --multiline -C5Length of output: 32283
🏁 Script executed:
#!/bin/bash
# Check Apollo cache usage patterns in the codebase
rg -n 'cache\.modify' --multiline -C3 | head -40Length of output: 4197
🏁 Script executed:
#!/bin/bash
# Find cache.identify usage patterns
rg -n 'cache\.identify' --multiline -C2 | head -30Length of output: 3017
Fix cache.modify to handle array return and guard undefined cache.identify
The mutation returns an array [ProductGroups], not a single object. Passing the array to cache.identify() returns undefined, causing cache.modify() to corrupt ROOT_QUERY. Additionally, skip cache update when no fields to modify.
return saveProductGroups({
...operationVariables,
variables,
- update: (cache, { data }) => {
- const productGroupsBulkInsert = data?.productGroupsBulkInsert;
- if (!productGroupsBulkInsert) return;
-
- cache.modify({
- id: cache.identify(productGroupsBulkInsert),
- fields: fieldsToUpdate,
- });
- },
+ update: fields.length
+ ? (cache, { data }) => {
+ const items = data?.productGroupsBulkInsert;
+ if (!Array.isArray(items) || items.length === 0) return;
+ items.forEach((item) => {
+ const itemId = cache.identify(item);
+ if (itemId) {
+ cache.modify({ id: itemId, fields: fieldsToUpdate });
+ }
+ });
+ }
+ : undefined,
});Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/plugins/sales_ui/src/modules/pos/hooks/usePosEditProductGroup.tsx
around lines 20 to 33, the mutation returns an array but the code calls
cache.identify on the whole array (yielding undefined) and unconditionally calls
cache.modify; change the update logic to treat productGroupsBulkInsert as an
array, skip the update if fieldsToUpdate is falsy or empty, and for each product
group in the array call cache.identify(item) and only call cache.modify when
identify returns a valid id; also guard against productGroupsBulkInsert being
undefined before iterating.
| catProdMappings: [ | ||
| ...productValues.catProdMappings.map((item) => ({ | ||
| _id: `${Math.random()}`, | ||
| name: item.name, | ||
| code: item.code, | ||
| categoryId: item.categoryId, | ||
| productId: item.productId, | ||
| })), | ||
| ], |
There was a problem hiding this comment.
🧩 Analysis chain
Avoid random _id for mappings
Random IDs break update semantics and can create duplicates. Preserve existing _id if present; otherwise omit it and let the API generate one.
- catProdMappings: [
- ...productValues.catProdMappings.map((item) => ({
- _id: `${Math.random()}`,
- name: item.name,
- code: item.code,
- categoryId: item.categoryId,
- productId: item.productId,
- })),
- ],
+ catProdMappings: productValues.catProdMappings.map(item => ({
+ ...(item._id ? { _id: item._id } : {}),
+ name: item.name,
+ code: item.code,
+ categoryId: item.categoryId,
+ productId: item.productId,
+ })),🏁 Script executed:
#!/bin/bash
# Verify whether _id is optional in mapping schema
rg -n -C2 -P 'catProdMappingSchema|catProdMappings' frontend
rg -n -C2 -P 'z\.object\(\{[^}]*_id' frontendLength of output: 14881
Avoid random _id for mappings
Random IDs break update semantics. MongoDB database drivers by default generate an ObjectID identifier that is assigned to the _id field of each document. Preserve existing _id if present; otherwise omit it and let the API generate one. The schema confirms _id is optional (z.string().optional() in formSchema.ts), so passing it is not required.
- catProdMappings: [
- ...productValues.catProdMappings.map((item) => ({
- _id: `${Math.random()}`,
- name: item.name,
- code: item.code,
- categoryId: item.categoryId,
- productId: item.productId,
- })),
- ],
+ catProdMappings: productValues.catProdMappings.map(item => ({
+ ...(item._id ? { _id: item._id } : {}),
+ name: item.name,
+ code: item.code,
+ categoryId: item.categoryId,
+ productId: item.productId,
+ })),🤖 Prompt for AI Agents
In frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx
around lines 164 to 172, the code assigns a random string to _id for each
catProdMappings entry which breaks update semantics; change the mapping so it
preserves item._id when it exists and otherwise does not set _id (omit the
field) so the API/database can generate it; implement this by only including _id
in the mapped object when item._id is present (or by setting it to
undefined/omitting the property) and keep all other fields unchanged.
| toast({ | ||
| title: TOAST_MESSAGES.POS_EDITED, | ||
| description: `Saved`, | ||
| variant: 'destructive', | ||
| }); | ||
| if (posEditResponse.data?.posEdit?._id) { | ||
| await productGroupSave({ variables: finalDataProductGroup }, []); | ||
| refetchProductGroup(); | ||
| } |
There was a problem hiding this comment.
Toast timing/variant
Don’t show success before mutations complete, and use a success variant.
- toast({
- title: TOAST_MESSAGES.POS_EDITED,
- description: `Saved`,
- variant: 'destructive',
- });
- if (posEditResponse.data?.posEdit?._id) {
- await productGroupSave({ variables: finalDataProductGroup }, []);
- refetchProductGroup();
- }
+ if (posEditResponse.data?.posEdit?._id) {
+ await productGroupSave({ variables: finalDataProductGroup }, []);
+ await refetchProductGroup();
+ toast({ title: TOAST_MESSAGES.POS_EDITED, variant: 'success' });
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| toast({ | |
| title: TOAST_MESSAGES.POS_EDITED, | |
| description: `Saved`, | |
| variant: 'destructive', | |
| }); | |
| if (posEditResponse.data?.posEdit?._id) { | |
| await productGroupSave({ variables: finalDataProductGroup }, []); | |
| refetchProductGroup(); | |
| } | |
| if (posEditResponse.data?.posEdit?._id) { | |
| await productGroupSave({ variables: finalDataProductGroup }, []); | |
| await refetchProductGroup(); | |
| toast({ title: TOAST_MESSAGES.POS_EDITED, variant: 'success' }); | |
| } |
🤖 Prompt for AI Agents
In frontend/plugins/sales_ui/src/modules/pos/pos-detail/components/posDetail.tsx
around lines 214 to 222, the toast is shown before the related mutations finish
and uses the destructive variant; move the toast so it runs after the
productGroupSave and refetchProductGroup complete, await productGroupSave and
await refetchProductGroup (if they return promises), and change the toast
variant to 'success' (and adjust description to reflect completion, e.g. "Saved"
after awaits). Ensure the toast is only shown when the mutation(s) succeed.
* fix(posclient): merge * fix(pos): moved into sales * fix(posclient): update * fix(posclient): code reviews improvements * fix(posclient): code reviews * fix(posclient): code reviews * fix(posclient): code reviews * fix(posclient): code reviews * fix(posclient): code reviews * fix(posclient): code reviews * fix: pos productgroup&edits --------- Co-authored-by: orshih <[email protected]>
Summary by Sourcery
Support product group management within POS creation and editing flows by extending types, form schema, and UI, and persist groups via a dedicated GraphQL mutation. Also enforce default onServer flags and streamline TRPC crudData behavior.
New Features:
Bug Fixes:
Enhancements:
Important
Enhance POS product group management with backend mutations and frontend UI updates for CRUD operations.
posclient.ts, changecrudDatafromquerytomutationto handle data modifications.sendPosclientMessageininitWorker.tsto usemutationforcrudDataactions.productGroupsBulkInsertmutation inpos.tsto handle bulk insert and update of product groups.POS_EDITEDmessage toTOAST_MESSAGESinconstants/index.tsx.formSchema.tsto includeproductGroupsinproductSchema.ProductForminproduct.tsxto handle product group fields and interactions.usePosEditProductGrouphook inusePosEditProductGroup.tsxfor product group mutations.posDetail.tsxto integrate product group editing and saving logic.This description was created by
for c859769. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Refactor