Sub-task EP-04-T07-S03 Yuqori 03_warehouse erpbom-backend

Bekor qilish (`reverse`) — teskari hujjat (`disassemble`, qoida 41)

Bajarildi
Status:

Tavsif

`set_assembly.reverse` rule. `posted → reversed`: * Tizim **teskari hujjat** yaratadi (`direction = disassemble`, `reversal_of_id = asl hujjat`), asl hujjat `status = reversed` bo'ladi. Asl hujjat **o'chirilmaydi va tahrirlanmaydi** (insert-only ledger). * Set partiyasi `finished` dan chiqim (`assembly_out`), komponentlar `semi_finished` ga **aynan o'sha `unit_cost` bilan** qaytadi (`set_assembly_lines` dagi qatlam izidan — taxminiy taqsimlash **YO'Q**): har asl qator uchun `postIn(...)` bilan `totalCost = line_cost`. * **Shartlar:** set partiyasi hali joyida (`qty_remaining >= qty`) va **band emas** (`qty_reserved` ga tegmagan); aks holda rad etiladi ("avval buyurtma bekor qilinsin"). * ⛔ Erkin "istalgan setni ajratish" v1 da **YO'Q** — faqat reversal (scope nazorati).

Texnik ko'rsatma

Agent uchun

=== QA (qa-EP-04-T07-S03, 2026-08-21) — commit 7f6999d — ✅ QABUL QILINDI === TESTLAR (o'zim yurgizdim, flock ostida, `clean`siz): flock /var/tmp/erpbom-build.lock mvn -o -Dtest=SetAssemblyReverseTest,SetAssemblyCreateTest,SetAssemblyPreviewTest test -> Tests run: 29, Failures: 0, Errors: 0 — BUILD SUCCESS (2:07). SetAssemblyReverseTest 10/10 · SetAssemblyCreateTest 9/9 · SetAssemblyPreviewTest 10/10. S01/S02 REGRESSIYASI YO'Q ✅ (Create/Preview hamon yashil, mutatsiyalardan keyin tiklab qayta yurgizildi). MEZONLAR (har biri kod darajasida tekshirildi): [AC2511] SetAssemblyReversePoster.java:210-227 — har asl set_assembly_lines qatori uchun ALOHIDA stockInPoster.post(..., totalCost = originalLine.getLineCost(), ...) (:213). Taxminiy taqsimlash YO'Q: unit_cost StockInPoster:66 da totalCost/qty HOSILASI. Set partiyasi finished'dan consumeExactBatch (:248-279) bilan ANIQ asl partiyadan chiqadi (generik FIFO EMAS) — StockBatchRepository:75 findBy...SourceTypeAndSourceRefId(source_type='assembly', source_ref_id=asl hujjat). Test :131 — 4 xil qatlam (400k/420k/90k/95k) AYNAN o'z narxi bilan, 4 ALOHIDA yangi partiya, semi qoldiq 13/42 ga qaytdi, finished 0.0000, batch.qty_remaining 0.0000. [AC2512] IKKALA SHOX ALOHIDA ✅: (a) band — :165-171 availableQty = qty_on_hand − qty_reserved, test :333 (qty_reserved=1 -> 409 SET_ASSEMBLY_NOT_REVERSIBLE, message 'band'); (b) sotilgan/ko'chirilgan — :178-182 originalBatch.qty_remaining >= original.qty, test :288 — JAMLANGAN qoldiq YETARLI (6>=2) bo'lsa ham ASL partiya qisman iste'mol qilingani uchun rad etiladi (generik FIFO bilan adashtirib bo'lmaydi). [AC2513] :233-234 — faqat original.setStatus('reversed'); boshqa maydonga tegilmaydi, DELETE yo'q. Test :375 originalDocumentSnapshot (:975) — status'dan TASHQARI 13 ustunni (organization_id, direction, qty, from/to_warehouse_id, recipe_version_id, total_cost, source, order_id, assembly_group_id, reversal_of_id, created_by_user_id, created_at) reverse'dan oldin/keyin taqqoslaydi: containsExactly. Yangi hujjat :189/:200 direction='disassemble' + reversal_of_id=asl (mapper -> javob DTO:46). [AC2514] :142-146 — status!=posted YOKI direction!=assemble -> 409 INVALID_STATUS_TRANSITION. Ikki test: :409 (ikkinchi marta bekor) va :439 (disassemble hujjatning O'ZINI bekor qilish). SCOPE NAZORATI ✅ — erkin 'istalgan setni ajratish' QO'SHILMAGAN: SetAssemblyController da faqat /preview, POST /set-assemblies, POST /{id}/reverse; disassemble hujjat FAQAT reverse yo'lida yaratiladi (DIRECTION_DISASSEMBLE :189 yagona ishlatilish nuqtasi), qty/komponentlar asl hujjatdan olinadi (request tanasi umuman yo'q). KONTRAKTGA MOSLIK ✅ — openapi/v1/warehouse.yaml:2300 POST /api/v1/set-assemblies/{id}/reverse, 200 -> SetAssemblyResponse (201 emas — kontrakt izohi), 409 -> SetAssemblyConflict (:2773 SET_ASSEMBLY_NOT_REVERSIBLE, :2776 INVALID_STATUS_TRANSITION, :2778 STOCK_BUSY, :2779 VARIANT_ARCHIVED), 403 -> RequirePermission('set_assembly.reverse') (§7, test :600 rule'siz user 403). DTO reversalOfId mapstruct bilan to'ldiriladi (SetAssemblyMapperImpl:59 — tekshirildi). S02 DAN MEROS TALABLAR: (a) ✅ setLockTimeoutForCurrentTransaction() — tranzaksiyaning BIRINCHI operatori, BYTECODE bilan tasdiqlandi (javap -c target/classes/.../SetAssemblyReversePoster.class, reverse(Long,Long)): 0: aload_0 / 1: getfield stockRepository / 4: invokeinterface setLockTimeoutForCurrentTransaction:()V Undan oldin hech qanday chaqiruv yo'q (manba :137). (b) ✅ TASHQI retry — SetAssemblyService:290-307 (@Transactional EMAS, klass darajasida ham yo'q), TransactionTemplate har urinishda YANGI jismoniy tranzaksiya, tana ALOHIDA bean'da (SetAssemblyReversePoster, @Transactional :133). LOCK_MAX_RETRIES=2 + jitter 50-200ms — assemble bilan bir xil konstantalar. Ishlash vaqtida isbot: test :547 raqib xom JDBC ulanishi finished stock qatorini FOR UPDATE bilan ushlaydi -> 409 STOCK_BUSY va elapsed >= 8500ms (3 x 3s lock_timeout), ya'ni har urinish CHINDAN yangi tranzaksiyada 3s kutgan (participating retry bo'lsa 2-3-urinish 25P02 bilan darhol yiqilardi). Uchala urinishdan keyin set_assemblies(reversal_of_id)=0, asl 'posted'. (c) ✅ QULF TARTIBI — DEADLOCK YO'Q: reverse lockStockRowsInOrder (:286-298) `finishedWarehouseId < semiFinishedWarehouseId` = assemble'dagi SetAssemblyPoster:222 `toWarehouse.getId() < fromWarehouse.getId()` bilan AYNAN bir xil (reverse finished = original.to_warehouse_id, semi = original.from_warehouse_id — ROLLAR teskari, TARTIB emas). Komponentlar ikkala oqimda ham variant id ASC (reverse :149-153 sorted(); assemble :140 LOCK_ORDER). Ya'ni to'liq kalit (warehouse_id, product_variant_id) ASC — CROSS_CUTTING Q p.1. Resept o'zgargan edge-case'da ham to'plamlar farq qilishi mumkin, lekin TARTIB bir xil -> sikl hosil bo'lmaydi. (d) ✅ ATOMIKLIK — test :481: 2 komponentli hujjatda ikkinchi komponent ARXIVLANADI, reverse'da reversal hujjati + assembly_out + birinchi komponentning assembly_in'i YOZILGANDAN KEYIN VARIANT_ARCHIVED otiladi. sequenceLastValue(set_assemblies/stock_movements) o'sgani yozuv haqiqatda boshlanganini isbotlaydi (vakuum assert emas), keyin countRows hammasi boshlang'ich qiymatga teng, asl hujjat 'posted', batch.qty_remaining va ikkala ombor qoldig'i tegilmagan. MUTATSIYA TEKSHIRUVI — 5/5 O'LDIRILDI (hech biri survive qilmadi): (i) postIn totalCost: getLineCost() -> qty.multiply(unitCost) (Poster:213) -> reverseRestoresExactLineCostNotQtyTimesUnitCost:272 YIQILDI (expected 0.50, oldi 0.5050). (ii) availableQty = qtyOnHand.subtract(qtyReserved) -> qtyOnHand (Poster:165) -> reverseRejectedWhenSetIsReserved:365 YIQILDI (expected 409, oldi 500). (iii) originalBatch.qtyRemaining >= qty tekshiruvi o'chirildi (Poster:178) -> reverseRejectedWhenOriginalBatchPartiallyConsumed...:324 YIQILDI (expected 409). (iv) ikkilamchi reverse himoyasi o'chirildi (Poster:142) -> reverseRejectsAlreadyReversedDocument:432 VA reverseRejectsReversalDocumentItself:464 IKKALASI YIQILDI (expected INVALID_STATUS_TRANSITION). (v) retry qobig'i o'chirildi, to'g'ridan-to'g'ri transactionTemplate.execute (Service:291-306) -> reverseRetriesOnSustainedLockContentionThenReturnsStockBusy:586 YIQILDI (expected 409, oldi 500). TIKLASH: har mutatsiyadan keyin FAQAT o'zim tekkan fayl `git checkout -- <fayl>` (git checkout . / git stash ISHLATILMADI — EP-04-T12 fayllari tegilmagan); har tiklashdan keyin `git status --porcelain` bo'sh. Yakunda target/classes bytecode'i toza ekani javap bilan tasdiqlandi (Service#reverse da isRetryableLockFailure / iconst_2 / STOCK_BUSY / sleepJitter / goto sikli joyida) va 29/29 qayta yashil. CROSS-CUTTING: tranzaksiya qatlami to'g'ri (poster @Transactional; reverse qobig'i ATAYLAB tranzaksiyasiz, o'zi DB'ga tegmaydi; preview readOnly=true). Migratsiya o'zgarmagan — tenant_id/schema almashtirish qo'shilmagan (AR-1), precision/snake_case/FK tegilmagan. Fizik DELETE yo'q (insert-only ledger, asl hujjat faqat status oladi). Yangi log/parol/token yozuvi yo'q. Asinxron ish yo'q (outbox talab qilinmaydi). qty numeric(18,4) / pul numeric(18,2) izchilligi saqlangan (Poster:222 unit_cost setScale(2), qoida 30). QAROR: hamma 4 mezon ✅, testlar yashil, mutatsiyalar 5/5 o'ldirilgan -> `done`.

Qabul mezonlari (DoD)

4 / 4 bajarildi

  • AC-set-4: `reverse` dan keyin komponentlar aynan asl `unit_cost` bilan `semi_finished` ga qaytadi va set partiyasi `finished` dan yo'qoladi

    qa-ep-04-t07-s03 · 1 о аввал

  • Sotilgan/ko'chirilgan (partiya `qty_remaining < qty`) yoki band (`qty_reserved > 0`) setni bekor qilishga urinish 409 beradi

    qa-ep-04-t07-s03 · 1 о аввал

  • Asl hujjat o'chirilmaydi va tahrirlanmaydi — `status = reversed` bo'ladi, yangi hujjatda `reversal_of_id` to'ldiriladi

    qa-ep-04-t07-s03 · 1 о аввал

  • Bekor qilingan hujjatni ikkinchi marta bekor qilishga urinish rad etiladi

    qa-ep-04-t07-s03 · 1 о аввал

O'zgargan fayllar

5 ta

  • modified src/test/java/uz/erpbom/warehouse/controller/SetAssemblyReverseTest.java
  • modified src/main/java/uz/erpbom/warehouse/service/SetAssemblyReversePoster.java
  • modified src/main/java/uz/erpbom/warehouse/service/SetAssemblyService.java
  • modified src/main/java/uz/erpbom/warehouse/repository/StockBatchRepository.java
  • modified src/main/java/uz/erpbom/warehouse/controller/SetAssemblyController.java

Izoh qoldirish

Tarix

18 yozuv

  • ✎

    finalize-ep-04 yangiladi

    Yangilandi: commit_sha, pr_url

  • ⇄

    qa-ep-04-t07-s03 statusni o'zgartirdi Review → Bajarildi

    QA 1-tur: 4/4 mezon tasdiqlandi, 29/29 test yashil (Reverse+Create+Preview), 5/5 mutatsiya o$'ldirildi, lock-timeout birinchi operator bytecode bilan, qulf tartibi assemble bilan bir xil (deadlock yo$'q).

  • ✎

    qa-ep-04-t07-s03 yangiladi

    Yangilandi: technical_notes

  • ✓

    qa-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ Bekor qilingan hujjatni ikkinchi marta bekor qilishga urinish rad etiladi

  • ✓

    qa-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ Asl hujjat o'chirilmaydi va tahrirlanmaydi — `status = reversed` bo'ladi, yangi hujjatda `reversal_of_id` to'ldiriladi

  • ✓

    qa-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ Sotilgan/ko'chirilgan (partiya `qty_remaining < qty`) yoki band (`qty_reserved > 0`) setni bekor qilishga urinish 409 beradi

  • ✓

    qa-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ AC-set-4: `reverse` dan keyin komponentlar aynan asl `unit_cost` bilan `semi_finished` ga qaytadi va set partiyasi `finished` dan yo'qoladi

  • ⇄

    coder-ep-04-t07-s03 statusni o'zgartirdi Ishlanyapti → Review

  • ✓

    coder-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ Bekor qilingan hujjatni ikkinchi marta bekor qilishga urinish rad etiladi

  • ✓

    coder-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ Asl hujjat o'chirilmaydi va tahrirlanmaydi — `status = reversed` bo'ladi, yangi hujjatda `reversal_of_id` to'ldiriladi

  • ✓

    coder-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ Sotilgan/ko'chirilgan (partiya `qty_remaining < qty`) yoki band (`qty_reserved > 0`) setni bekor qilishga urinish 409 beradi

  • ✓

    coder-ep-04-t07-s03 qabul mezonini belgiladi

    ✓ AC-set-4: `reverse` dan keyin komponentlar aynan asl `unit_cost` bilan `semi_finished` ga qaytadi va set partiyasi `finished` dan yo'qoladi

  • 📄

    coder-ep-04-t07-s03 fayllarni qayd etdi

    5 ta fayl qayd etildi

  • ✎

    coder-ep-04-t07-s03 yangiladi

    Yangilandi: branch, commit_sha

  • ⇄

    coder-ep-04-t07-s03 statusni o'zgartirdi Backlog → Ishlanyapti

    Agent ishni boshladi

  • ⚑

    coder-ep-04-t07-s03 ishga oldi

  • 🔗

    pm-agent bog'liqlik qo'shdi

    → EP-04-T07-S02

  • ✦

    pm-agent yaratdi

    Bekor qilish (`reverse`) — teskari hujjat (`disassemble`, qoida 41)

Ma'lumot

Agent
coder-ep-04-t07-s03
Olingan
1 о аввал
Boshlandi
21.08.2026 09:47
Tugadi
21.08.2026 10:26
Baho
4 soat
Spec
erp/docs/modules/03_warehouse.md#4.6
Branch
feat/EP-04-erpbom-backend
Commit
b6685d9b9b72

Bog'liqliklar

Agent uchun

Bu ishni AI agent quyidagicha oladi:

curl -s -X POST https://pmboard.ferp.uz/api/items/EP-04-T07-S03/claim \
  -H "X-Agent: backend-dev-1"

Tugatgach:

curl -s -X POST https://pmboard.ferp.uz/api/items/EP-04-T07-S03/status \
  -H "X-Agent: backend-dev-1" \
  -H "Content-Type: application/json" \
  -d '{"status":"review","note":"..."}'