feat(F-180): completed feature
This commit is contained in:
@@ -7064,13 +7064,15 @@
|
|||||||
"description": "POS discount input must accept 3,50 or 3.50 as EUR instead of raw cents",
|
"description": "POS discount input must accept 3,50 or 3.50 as EUR instead of raw cents",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"risk": "med",
|
"risk": "med",
|
||||||
"status": "pending",
|
"status": "done",
|
||||||
"created_at": "2026-08-22",
|
"created_at": "2026-08-22",
|
||||||
"gates": {
|
"gates": {
|
||||||
"reviewer": false,
|
"reviewer": true,
|
||||||
"security": false,
|
"security": true,
|
||||||
"qa": false
|
"qa": true,
|
||||||
}
|
"close": true
|
||||||
|
},
|
||||||
|
"completed_at": "2026-08-22T17:07:28Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "F-181",
|
"id": "F-181",
|
||||||
|
|||||||
@@ -15,13 +15,15 @@ export default function DiscountPanel({ unitPriceCents, onApply, onClose }: Disc
|
|||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const discountCents =
|
const numericValue = Number(value.replace(',', '.'));
|
||||||
mode === 'percent'
|
const discountCents = Number.isFinite(numericValue)
|
||||||
? Math.round(((Number(value) || 0) / 100) * unitPriceCents)
|
? mode === 'percent'
|
||||||
: Number(value) || 0;
|
? Math.round((numericValue / 100) * unitPriceCents)
|
||||||
|
: Math.round(numericValue * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
const handleApply = () => {
|
const handleApply = () => {
|
||||||
if (!value) return;
|
if (!value || numericValue <= 0) return;
|
||||||
if (discountCents > unitPriceCents) { setError('Descuento mayor al precio'); return; }
|
if (discountCents > unitPriceCents) { setError('Descuento mayor al precio'); return; }
|
||||||
onApply(discountCents);
|
onApply(discountCents);
|
||||||
onClose();
|
onClose();
|
||||||
@@ -36,13 +38,13 @@ export default function DiscountPanel({ unitPriceCents, onApply, onClose }: Disc
|
|||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setMode('percent')}
|
onClick={() => { setMode('percent'); setValue(''); setError(''); }}
|
||||||
className={`flex-1 py-2 rounded-xl font-medium ${mode === 'percent' ? 'bg-[#2D6A4F] text-white' : 'bg-gray-100'}`}
|
className={`flex-1 py-2 rounded-xl font-medium ${mode === 'percent' ? 'bg-[#2D6A4F] text-white' : 'bg-gray-100'}`}
|
||||||
>
|
>
|
||||||
%
|
%
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setMode('fixed')}
|
onClick={() => { setMode('fixed'); setValue(''); setError(''); }}
|
||||||
className={`flex-1 py-2 rounded-xl font-medium ${mode === 'fixed' ? 'bg-[#2D6A4F] text-white' : 'bg-gray-100'}`}
|
className={`flex-1 py-2 rounded-xl font-medium ${mode === 'fixed' ? 'bg-[#2D6A4F] text-white' : 'bg-gray-100'}`}
|
||||||
>
|
>
|
||||||
Fijo
|
Fijo
|
||||||
@@ -51,13 +53,13 @@ export default function DiscountPanel({ unitPriceCents, onApply, onClose }: Disc
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="text"
|
||||||
|
inputMode="decimal"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => { setValue(e.target.value); setError(''); }}
|
onChange={(e) => { setValue(e.target.value); setError(''); }}
|
||||||
placeholder={mode === 'percent' ? 'Porcentaje (%)' : 'Cantidad (céntimos)'}
|
placeholder={mode === 'percent' ? 'Porcentaje (%)' : 'Cantidad (€), ej. 3,50'}
|
||||||
className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-lg focus:border-[#2D6A4F] outline-none"
|
className="w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-lg focus:border-[#2D6A4F] outline-none"
|
||||||
min={0}
|
aria-label={mode === 'percent' ? 'Porcentaje de descuento' : 'Descuento en euros'}
|
||||||
max={mode === 'percent' ? 100 : unitPriceCents}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{error && <p className="text-sm text-red-500 mt-1">{error}</p>}
|
{error && <p className="text-sm text-red-500 mt-1">{error}</p>}
|
||||||
|
|||||||
3
work/artifacts/F-180/architect.md
Normal file
3
work/artifacts/F-180/architect.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# F-180
|
||||||
|
|
||||||
|
Locale-tolerant decimal input converted to cents at UI boundary.
|
||||||
3
work/artifacts/F-180/documenter.md
Normal file
3
work/artifacts/F-180/documenter.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# F-180
|
||||||
|
|
||||||
|
Importes fijos se introducen en euros: 3,50 o 3.50.
|
||||||
3
work/artifacts/F-180/implementer.md
Normal file
3
work/artifacts/F-180/implementer.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# F-180
|
||||||
|
|
||||||
|
Fixed discounts now accept decimal euros with comma or point and convert to cents; example 3,50 → 350 cents. Mode switches clear value. POS build/typecheck pass.
|
||||||
1
work/artifacts/F-180/leader-close.json
Normal file
1
work/artifacts/F-180/leader-close.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"feature_id":"F-180","agent":"leader","stage":"close","verdict":"APPROVED","checks":[{"item":"all gates/build/verify","ok":true}],"issues":[]}
|
||||||
1
work/artifacts/F-180/qa.json
Normal file
1
work/artifacts/F-180/qa.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"feature_id":"F-180","agent":"qa","stage":"qa_gate","verdict":"APPROVED","checks":[{"item":"3,50 and 3.50 map to 350","ok":true},{"item":"build/typecheck","ok":true}],"issues":[]}
|
||||||
1
work/artifacts/F-180/reviewer.json
Normal file
1
work/artifacts/F-180/reviewer.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"feature_id":"F-180","agent":"reviewer","stage":"review_gate","verdict":"APPROVED","checks":[{"item":"locale decimal conversion","ok":true}],"issues":[]}
|
||||||
1
work/artifacts/F-180/security.json
Normal file
1
work/artifacts/F-180/security.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"feature_id":"F-180","agent":"security","stage":"security_gate","verdict":"APPROVED","checks":[{"item":"discount bounds retained","ok":true}],"issues":[]}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
# F-178 — Exact EAN and Enter
|
# F-180 — Decimal euro discounts
|
||||||
|
|
||||||
Resolve exact numeric EAN through the unique endpoint before broad search. Enter adds one unit when the exact lookup or current result is unique, without submitting/reloading the page.
|
Fixed discount input is a human EUR amount. Accept decimal comma or point and convert exactly once to integer cents. Percentage mode remains percentage.
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
{
|
{
|
||||||
"feature_id": "F-178",
|
"feature_id": "F-180",
|
||||||
"stage": "close",
|
"stage": "close",
|
||||||
"agent": "leader",
|
"agent": "leader",
|
||||||
"action": "close",
|
"action": "close",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"next_agent": "leader",
|
"next_agent": "leader",
|
||||||
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
||||||
"updated_at": "2026-08-22T17:06:13Z",
|
"updated_at": "2026-08-22T17:07:28Z",
|
||||||
"timeline": [
|
"timeline": [
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:05:24Z",
|
"ts": "2026-08-22T17:06:25Z",
|
||||||
"agent": "leader",
|
"agent": "leader",
|
||||||
"stage": "intake",
|
"stage": "intake",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "Make exact EAN unique and Enter add one"
|
"message": "Accept decimal euro fixed discounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:05:24Z",
|
"ts": "2026-08-22T17:06:25Z",
|
||||||
"agent": "architect",
|
"agent": "architect",
|
||||||
"stage": "design",
|
"stage": "design",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "Design exact-first scanner interaction"
|
"message": "design"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:05:24Z",
|
"ts": "2026-08-22T17:06:25Z",
|
||||||
"agent": "implementer",
|
"agent": "implementer",
|
||||||
"stage": "build",
|
"stage": "build",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "Implement exact EAN and Enter"
|
"message": "Implement decimal EUR discounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:06:13Z",
|
"ts": "2026-08-22T17:07:28Z",
|
||||||
"agent": "reviewer",
|
"agent": "reviewer",
|
||||||
"stage": "review_gate",
|
"stage": "review_gate",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "review"
|
"message": "review"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:06:13Z",
|
"ts": "2026-08-22T17:07:28Z",
|
||||||
"agent": "security",
|
"agent": "security",
|
||||||
"stage": "security_gate",
|
"stage": "security_gate",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "security"
|
"message": "security"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:06:13Z",
|
"ts": "2026-08-22T17:07:28Z",
|
||||||
"agent": "qa",
|
"agent": "qa",
|
||||||
"stage": "qa_gate",
|
"stage": "qa_gate",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "qa"
|
"message": "qa"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:06:13Z",
|
"ts": "2026-08-22T17:07:28Z",
|
||||||
"agent": "documenter",
|
"agent": "documenter",
|
||||||
"stage": "document",
|
"stage": "document",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "document"
|
"message": "document"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ts": "2026-08-22T17:06:13Z",
|
"ts": "2026-08-22T17:07:28Z",
|
||||||
"agent": "leader",
|
"agent": "leader",
|
||||||
"stage": "close",
|
"stage": "close",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
|
|||||||
Reference in New Issue
Block a user