Staging - #375
Open
MostafaKadry wants to merge 123 commits into
Open
Staging#375MostafaKadry wants to merge 123 commits into
MostafaKadry wants to merge 123 commits into
Conversation
Adds a "LIFO Cart Order (Newest on Top)" POS setting. When enabled and no explicit cart sort is active, the most recently added item is shown at the top of the cart instead of the bottom — handy on long carts where the cashier wants to see what was just scanned. - New cart_lifo check field on POS Settings (default off) + backend constants (POS_SETTINGS_FIELDS / DEFAULT_POS_SETTINGS). - posSettings store exposes a cartLifo computed. - useCartSort accepts an optional lifoMode; reverses the list when LIFO is on and no sort column is selected. Explicit sorts are unaffected. - InvoiceCart passes the setting through; toggle lives in the Sales Operations settings group so it takes effect live via reloadSettings(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Credit (Pay-on-Account) sales inflated the POS shift closing total.
`_process_invoice()` added the full `base_grand_total` of every invoice to
the sales summary (`grand_total`/`net_total`/`sales_total`) and the per-row
amount, regardless of how much was actually paid. The cash Payment
Reconciliation, however, is built only from real payment rows — so a pure
credit sale pushed Net Sales up by the full amount while contributing 0 to
the drawer, leaving the two figures inconsistent within a single shift.
For non-return invoices the money summaries and the per-row `grand_total`
now use the amount actually collected (`base_paid_amount`): a pure credit
sale contributes 0, a partial sale contributes only its down-payment, and
`net_total` is scaled by the paid ratio. The full invoice value is preserved
in `transaction_amount`; display-only `invoice_total` and
`outstanding_amount` are added for the dialog badge and stripped before the
child-table set. Returns, quantities and tax accrual are unchanged.
The Close Shift dialog now shows an "On Account" / "Partially Paid" badge and
an "Unpaid: {amount}" sub-line on rows collected for less than their invoice
value. Adds unit tests for the collected-money totals and an Arabic string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…closing-total # Conflicts: # POS/src/components/ShiftClosingDialog.vue # pos_next/pos_next/doctype/pos_closing_shift/pos_closing_shift.py # pos_next/pos_next/doctype/pos_closing_shift/test_pos_closing_shift.py # pos_next/translations/ar.csv
…rder # Conflicts: # POS/src/components/sale/InvoiceCart.vue # POS/src/components/settings/POSSettings.vue # POS/src/composables/useCartSort.js # POS/src/stores/posEvents.js # POS/src/stores/posSettings.js
…evelop. Keep origin/develop baseline offers; GWP/auth Python stay in posnext_promotions. Free-bundle SI merge is no longer in the POS Next invoice class. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Updated App.vue to conditionally render AuthorizationDialog based on authGateInstalled. - Modified SelectInput.vue to support multiple selections and improved UI for selected options. - Refactored CreateCustomerDialog.vue to use requiresSplitCustomerName for customer name handling. - Adjusted InvoiceCart.vue to utilize the new wallet API and removed Magento dependencies. - Enhanced PromotionManagement.vue to include Gift Pool promotion type and related UI elements. - Introduced applyOfflineGiftPool function in posCart.js for handling gift pool discounts. - Updated various API endpoints to ensure compatibility with new promotional features. Co-authored-by: Cursor <cursoragent@cursor.com>
- Updated useInvoice.js to prevent free items from inflating stock checks and adjusted actual_qty to prefer original_stock. - Improved posCart.js to utilize new stock validation functions for accurate quantity checks, ensuring free items do not affect paid item stock validation. - Added utility functions in stockValidator.js for calculating stock quantities and validating item availability based on combined paid and free rows. - Modified _collect_stock_errors in invoices.py to sum quantities per item_code and warehouse, ensuring accurate stock error reporting. Co-authored-by: Cursor <cursoragent@cursor.com>
- Added conditional rendering for buttons in InvoiceCart.vue to prevent actions on locked free rows. - Updated quantity handling logic to ensure free items do not interfere with paid item interactions. - Introduced isLockedFreeRow function to streamline checks for free item status. - Enhanced posCart.js to manage free item quantities more effectively, ensuring accurate invoice processing for promotions like GWP. Co-authored-by: Cursor <cursoragent@cursor.com>
…alidation rules, and grant management
Addresses review from @MostafaKadry on PR #312: - paid_amount is the raw tendered total, so cash change given back to the customer (e.g. $20 tendered on a $15.50 sale) was inflating "collected" and driving outstanding_amount negative. Netted base_change out of base_paid before it's used anywhere, and reused the value for the existing cash-reconciliation subtraction instead of computing it twice. - net_total/grand_total scaled by paid_ratio for partial/credit sales, but the tax loop still aggregated the full tax_amount. Scaled taxes by the same ratio (1 for returns) so grand_total stays consistent with net_total + taxes. - Reformatted test_pos_closing_shift.py to tabs (repo convention) and added regression tests for both fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TngwKeKiHtHt6jFP9pcnY6
…I-Server change node-version to 22
…them Reworks the credit-sale closing fix so the cash-basis numbers live in their own fields rather than overwriting the accrual ones. grand_total, net_total and the taxes table go back to the invoiced amount. Two new read-only fields on POS Closing Shift carry the cash view: collected_amount money actually taken during the shift outstanding_total invoiced value still owed by customers invoiced == collected + outstanding, asserted in the tests. Why the change of approach: - POS Closing Shift.grand_total is persisted and already read elsewhere — get_shift_history() surfaces it as the "Sales" column and summary card. Redefining it as "collected" would have left historical rows meaning "invoiced" and new rows meaning "collected", with nothing to tell them apart. It now keeps its meaning and Shift History needs no change. - Scaling taxes by the paid ratio kept grand_total = net_total + taxes, but tax posts to the GL in full at invoice submission regardless of what was collected, so the scaled table could not be reconciled against the VAT accounts. With nothing scaled the identity holds by construction. - The ratio also misreported write-offs: a 100 invoice settled by 90 cash plus a 10 write-off reported net_total 90 and tax 0.9. Covered by a regression test. Also: - Port the same cash-basis logic to pos_closing_shift.js. The desk form mirrors _process_invoice(); leaving it on the old path would have made closing a shift from the desk and from the POS produce different totals for the same invoices. - Closing dialog gains "Collected" and "On Account" cards, so the cashier can still see what was sold, not only what was banked. Per-invoice badges now key off collected_amount, since grand_total is invoiced again. - Arabic strings for the three new labels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
…closing-total-followup
- Updated CreateCustomerDialog.vue to require a selected country code for customer submission and improved mobile number handling. - Enhanced posCart.js and posOffers.js to include gross and net amount calculations for offer validation, ensuring accurate eligibility checks based on item discounts. - Refactored customer group and territory resolution logic in customers.py to prefer non-group entries, improving customer creation reliability.
Dead assignment — the value is never read. It predates this branch, but ruff lints changed files, so it fails pre-commit for anything touching this module. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
Merges upstream/develop (42 commits) into the product management branch. Four files conflicted. All were resolved by taking develop's version and re-applying only the functional additions, so none of this branch's prettier churn is reintroduced: - itemSearch.js — reset to develop, then re-added upsertItemInList(), refreshItem() and the store export. Verified by normalizing both files through the same formatter: the delta against develop is now exactly those two functions, the export, and one let -> const. Previously the branch showed ~600 changed lines here, almost all quote style and trailing commas. - ManagementSlider.vue — took develop's formatting, re-applied the Products -> Stock Lookup rename and the new Product Management button. - POSSale.vue — took develop's `let` declarations. The biome-ignore comments are unnecessary; JS is linted by prettier/eslint in pre-commit, not biome. - ar.csv — kept both sides' new strings. No functional change to the feature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
The item-group scoping added in review covered get_products (the read
path). The same boundary was not enforced when writing, in two ways.
1. pos_profile was caller-supplied and never validated.
save_product() took the profile name straight from the request and
called frappe.get_cached_doc(), which does no permission check. The
guard that follows is:
if allowed_item_groups and item_group not in allowed_item_groups:
and _get_pos_profile_allowed_item_groups() documents that an empty
list means "no group restriction". So naming any POS Profile with no
item_groups rows made allowed_item_groups falsy and skipped the check
entirely — a cashier scoped to one branch could edit any Item in the
system. It also reached pricing, since the price is written to that
profile's selling_price_list.
Added _validate_pos_profile_access(), matching the POS Profile User
check already used in api/invoices.py and api/credit_sales.py, and
applied it to save_product, get_products and get_item_groups.
2. The update path never checked the item's current group.
Only the incoming item_group was validated, so passing the item_code
of an item in a disallowed group pulled it into an allowed one —
renaming, regrouping, disabling and repricing it on the way. Now the
existing group is validated before the item is mutated.
Neither is exploitable without Item write permission, which the feature
requires anyway. But the point of profile scoping is to hand product
management to a branch cashier without giving them the global item
master, and that boundary did not hold.
Also in this file:
- get_item_groups() had no permission check at all — any authenticated
user could enumerate any profile's item groups. Now requires Item read
plus profile access.
- item.image accepted an arbitrary string, so a crafted call could point
it at an external URL that the POS then renders. Restricted to Frappe
file paths. (Reachable from the desk too, so not introduced here.)
- Removed frappe.db.commit(). The framework commits at the end of a
successful request and rolls back on exception; committing here
defeats that and commits unrelated pending work.
- Sorted the import block (ruff I001), which otherwise fails pre-commit
for anyone touching this file.
Adds test_product_management.py — 10 mock-based tests, no DB. Verified
by mutation: removing the profile check fails
test_rejects_profile_the_user_is_not_assigned_to, and removing the
current-group check fails test_rejects_item_whose_current_group_is_out_of_scope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
Two formatting-only changes, no behaviour change: - ProductManagement.vue — ran the repo's prettier over the new file. It is a new file, so this is not churn against develop; pre-commit would rewrite it on merge anyway. - itemSearch.js — braced the four switch-case blocks that declare consts (eslint no-case-declarations, 8 errors). This is pre-existing on develop and only surfaces here because pre-commit lints changed files and this branch touches the file. Braces just scope the consts to their case. Verified afterwards that itemSearch.js and POSSale.vue still differ from develop only by this branch's functional additions — no formatting churn was reintroduced into either. Split out so it can be dropped independently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
Two defects stacked, so a failed save showed nothing at all.
1. handleError was undefined.
ProductManagement.vue destructures it from useToast():
const { showSuccess, showError, handleError } = useToast();
useToast() never exported handleError. All four catch blocks —
saveProduct, loadProducts, loadItemGroups, loadUOMs — therefore called
undefined(...), throwing a TypeError *inside the catch*. That
rejection is unhandled, so nothing reaches the user: the spinner stops
in `finally` and no toast appears.
The file was adapted from PromotionManagement.vue, which defines
handleError locally (line 1449) instead of taking it from useToast.
CouponManagement.vue has a third copy. Same class as the refreshItem()
issue already caught in review.
2. Reading _server_messages does not work for errors raised through the
app's `call` wrapper.
frappe-ui's call.js does not pass the response through. It builds a new
Error whose `.message` is only "<method> <exc_type>", and moves the
real text to `.messages` — already parsed out of _server_messages and
flattened to strings. So `error._server_messages` is undefined and the
user gets:
pos_next.api.product_management.save_product ValidationError
The local copies in PromotionManagement.vue and CouponManagement.vue
check only _server_messages, so they have this same blind spot.
Fixed at the root: parseErrorMessage + handleError now live in useToast
and are exported, so the existing call sites become correct unchanged.
parseErrorMessage handles both shapes — frappe-ui's `.messages` array
first, then raw `_server_messages` for the direct fetch() paths — strips
the HTML, de-duplicates, and ignores a `.message` that is just the
"<method> <exc_type>" string. handleError never throws, so a failing
error handler cannot turn a failed action into a silent one again.
Promotion/Coupon keep their local copies for now; they work for raw
responses and migrating them is out of scope here.
Verified against a real failure — an Item save rejected by the Shopify
on_update hook in ecommerce_integrations. Reconstructing the error object
exactly as frappe-ui/src/utils/call.js builds it, the toast goes from
"pos_next.api.product_management.save_product ValidationError" to
"Failed to decrypt key Shopify Account… Encryption key is invalid!
Please check site_config.json…". Null, plain Errors, empty message
arrays and exc_type-only errors all fall back to the default message.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
…ct image The form was capped at `max-w-2xl` (672px) inside a pane that is ~1500px wide on a 1080p screen, so roughly 830px sat empty while the product image was a 128x128 thumbnail. Layout - Dropped the max-w-2xl cap; the form now uses the pane up to 1400px. - Two columns from xl: fields on the start side, a large image panel on the end side. CSS grid flips this for RTL on its own, so the image stays on the end side in Arabic without extra rules. - The image panel is sticky on xl+, so it stays visible while scrolling the UOM conversions. - Below xl it collapses to one column with the image on top, capped at 320px so a square image does not fill a tablet screen. Image - 128x128 thumbnail -> a square panel that is 380px at xl and 460px at 1536px+. About 3x the linear size, 8-13x the area. - The image itself is the upload target: click anywhere on it, or drop a file onto it. Drag state is reflected on the border. - object-contain rather than object-cover, so product photos are not cropped. - Empty state is now an explicit dashed dropzone instead of a grey box. Fields - Grouped into Details / UOM Conversions / Options sections with headings, rather than one flat stack. - Product Name spans the full width; Item Group, UOM and Price share a row once there is room for it. - The two checkboxes became labelled cards with descriptions. - UOM conversion rows use an icon-only remove button, which stops the row wrapping in the narrower column. Breakpoints Tailwind breakpoints are viewport-based, not container-based, so a naive `sm:grid-cols-2` inside the fields column fires while that column is still ~190px. Column widths were worked out per viewport and the breakpoints chosen to match: two columns only from xl (fields 436px), inner grids only from 1536px (fields 599px). Note: `min-[1536px]:` is used rather than `2xl:` because frappe-ui's preset replaces theme.screens with sm/md/lg/xl only (frappe-ui/tailwind/plugin.js:212). `2xl:` compiles to nothing in this project — verified by checking the emitted CSS. Verified every new utility and arbitrary value is present in the built stylesheet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
…Settings
Mobile
The app is responsive (device-width viewport, useResponsivePayment's
isMobileView, mobile detection in performanceConfig, PWA), but this
dialog was not: a fixed 320px list beside the form does not fit a phone.
- Below md the list and the form are one pane at a time (master-detail),
with an explicit back arrow in the form header since the list is no
longer beside it. The arrow mirrors under rtl:.
- Full-bleed on phones; the 95vw/95vh rounded card returns at sm.
- Header, action bar and form padding tighten below sm; the action bar
wraps rather than overflowing, and the product title truncates.
Upload rules
Extensions and max size were hardcoded (2 MB, four MIME types), so the
picker accepted files the server then rejected. Both now come from
System Settings via get_product_image_settings():
- allowed_file_extensions is newline-separated, uppercase, without dots,
and empty means "no restriction" rather than "nothing allowed". It
covers every file type, so it is intersected with the image types this
screen can render — a site allowing CSV should not offer CSV as a
product image.
- max_file_size is stored in MB; get_max_file_size() resolves it to
bytes, falling back to site_config then Frappe's 25 MB default.
Validation matches on extension rather than MIME type, because browsers
report inconsistent types for the same file and the server checks the
extension too. If the fetch fails the screen keeps working on defaults;
the server validates on upload regardless.
The hint under the image now reads the live values:
Allowed file types: JPG, JPEG, PNG, GIF, WEBP — up to 25 MB.
These are configurable in System Settings. Upload happens when the
product is saved.
If the site allows no image types at all, that is stated explicitly
instead of leaving a picker that can never succeed.
Adds 5 tests (15 total) covering the empty-means-unrestricted rule, the
intersection with image types, the no-image-types case, messy operator
input (lowercase, leading dots, blank lines) and the MB-to-bytes
conversion. Verified against the live site: no restriction configured,
so all five types at 25 MB.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNMeLBCSBBYMm5k5zDNda4
- Updated EditItemDialog.vue to prevent price-list refresh for locked free items and scale rates when changing UOM for promotional items. - Modified InvoiceCart.vue to ensure free promotional rows remain promotion-owned, preventing unintended edits. - Enhanced posCart.js to apply UOM changes while preserving rates for free promotional items, ensuring accurate pricing and user feedback. This update aims to improve the handling of UOM changes and pricing integrity for promotional items in the sales process.
…ettings - Added `merge_pos_settings` function to ensure runtime integration flags (`miraaya_installed`, `magento_loyalty_available`) are always present in POS settings, even when not defined in the database. - Updated various modules to utilize the new function, ensuring that default settings are correctly merged with database values. - Enhanced test cases to validate the behavior of merged settings, maintaining expected defaults and integration flags. This update aims to improve the reliability of POS settings by ensuring critical flags are always available, enhancing integration capabilities.
… feature/thin-develop-split Resolve CreateCustomerDialog JSDoc conflict; keep detailed ISD defaulting comment. Co-authored-by: Cursor <cursoragent@cursor.com>
…ofiles by creation date feat(posCart): normalize pricing rules to prevent corruption from server responses feat(authorization): prevent multiple gated requests from overwriting state
Feature/thin develop split
frappe-ui selects portal at z-100 behind the z-300 management dialog, so the option list never appears. Use SelectInput and raise portaled select menus so cashiers can filter and edit coupons. Co-authored-by: Cursor <cursoragent@cursor.com>
…n-list fix(promotions): show coupon filter dropdowns above the overlay
…not-reflected-in-pos-screen Pn 111 item code settings not reflected in pos screen
…ify app behaviors - Added a section on bootstrapping dependencies for the `pos_next` app, detailing commands to ensure required and optional apps are installed on a site. - Included a table summarizing the behavior of `erpnext`, `posnext_promotions`, and `pos_next` during the bootstrap process. - Removed redundant module entries from `modules.txt` for clarity.
pos.html referenced /assets/nexus_demo/...bundle.js directly. That URL never changes and Frappe serves it with a 12-hour max-age, so any browser that had already opened POS kept running the previous build for half a day — including straight through the release that added the role switcher to the banner. The Desk picked the change up immediately, which made it look like a POS bug rather than a caching one. bundled_asset() resolves the same file through assets.json, so the URL carries the build hash and a new build is a new URL. include_style() also picks the mirrored css-rtl build for an Arabic session, which the hardcoded path never did. The tags moved to the end of <body>: vite parses <head>, and loose text there is liable to be relocated into the body, which would separate these from the conditional guarding them. That conditional keeps the tags off sites where nexus_demo isn't installed, which is what the old onerror="this.remove()" was for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9wyGLvRjk5q4rjDRrmn6t
Load the demo banner by its hashed URL, not a fixed path
F4/F8/F9 only skipped when uiStore.isAnyDialogOpen was true, but the session-lock screen and clear-cache overlay aren't registered in that dialog tracker, so the shortcuts still fired underneath them. Also fix F8 being a no-op when a customer is already assigned to the cart, since the search input isn't rendered in that state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
POSSale claimed the whole viewport with an inline height: 100vh; max-height: 100vh. Nothing above POS was accounted for, so when a host put chrome at the top — Frappe's navbar, the Nexus demo countdown banner, anything that pads the body — the page did not shrink, it moved. Its bottom ran off the screen by exactly the height of that chrome, taking the cart's Checkout and Hold buttons with it. On a desktop that is 48px and the buttons land underneath POSFooter, which is position: fixed and therefore does not move with the rest of the page: they render, they look enabled, and they swallow the click. On a phone the demo banner wraps to two rows and it is 86px, which is more than the footer is tall, so the buttons go off the bottom edge entirely. Either way the visitor cannot take a payment, which on a point of sale is the whole job. The page now sizes itself with flex — html/body/#app form a column and POSSale fills what is left — so it is whatever the host actually leaves it, with no number to keep in sync. 48px of banner, 86px of a two-row one, or nothing at all all work out on their own. The footer keeps position: fixed, and the integrity checks that enforce it are untouched. It publishes its measured height as --pos-footer-h instead, and the page reserves that band with padding-bottom, so the strip stays pinned and visible without sitting on top of the buttons. The footer owns the number, so restyling it moves the reservation with it rather than stranding a constant elsewhere. Verified in a browser at 1280x900, 1440x800 and 390x844, in a demo session with the banner up: Checkout on-screen, clear of the footer, no document overflow, and a real click through to a submitted invoice (ACC-SINV-2026-00015). jsdom does no layout, so there is no unit test that could have caught this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch-CLI-command-that-installs-dependency-apps-for-pos_next-on-a-given-site docs: update README with bootstrap dependencies instructions and clarify app behaviors
… and print format
…tcuts-f4-f8-f9 feat: implement F4/F8/F9 keyboard shortcuts
…-to-sales-invoice [PN 118] feat(invoice): add sales person and coupon details to invoice display and print format
…ewport fix(pos): the till hung off the bottom of the screen under host chrome
…invoice Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ries it in the GL Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
handleShiftOpened assigned the undeclared _initializedProfile (renamed to _initializedKey in ecfddc6), so the ReferenceError skipped startActivityTracking and the lock never fired until a page reload. Starting tracking also went through the throttled resetTimer, so no idle timer was armed until the first activity event. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Without it, migrate and install never create the POS Authorization tables, so submitting any POS return raises TableMissingError. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ource fix(credit): linked POS return credit booked on the wrong invoice; session lock after shift open
engahmed1190
force-pushed
the
develop
branch
from
September 24, 2026 18:12
1638361 to
82468c1
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.