---
# System prepended metadata

title: '[PH] Quotations'

---

# [PH] Quotations
---

# Table
## quotations
- qoutation_step ???

## inquiry_partner_recommendations
- tambah column invitation_type "smart_invite" | "custom_invite"
- ketika akan dipindah ke inquiry_suppliers, pastikan partner_id TIDAK duplicate.
- Jika ada 2 partner_id yang sama di-invite by smart_invite dan custom_invite, maka column inquiry_suppliers.smart_invite diisi TRUE.

### uml: class diagram
```plantuml
@startuml
package "Quotations" #DDDDDD {
    class bidding_submission_specs {
      +int id
      +int quotation_id
      +int partner_id
      +int product_id
      +jsonb spec_set
      +int position
      +bool suggested
      +string suggestion_reason
      +string other_reason
    }
  
    class bidding_submission_groups {
      -int id
      -int bidding_submission_spec_id fk
      -int quantity
      -int coverage_ids [INQUIRY_SHIPMENT_ID]
    }

    note right of bidding_submission_specs 
        product_id integer
        (gak perlu bikin relation)
    end note

  
    bidding_submission_groups *-- bidding_submission_specs
}
@enduml
```
## bidding_submission_specs
- quotation_id
- partner_id
- product_id integer (gak perlu bikin relation)
- spec_set [{}]
- position
- suggested (boolean)
- suggestion_reason
- other_reason

## bidding_submission_groups
- id
- bidding_submission_spec_id
- quantity
- coverage_ids [INQUIRY_SHIPMENT_ID]


## bidding_submisssions
- tambah column:
  + bidding_submission_group_id
  + shipping_group (city | address)
  + unit_price
- remove column:
  + quotation_id
  + product_id
  + specs
  + spec_set
  + suggested
  + partner_id
  + integration

## bidding_submisssion_coverages
### Columns:
- bidding_submission_id
- shipping_address_id
- shipping jsonb {}

## bidding_submission_packs
- bidding_submission_id
- quantity
- weight
- length
- width
- height

## bidding_submission_shippings
- bidding_submission_id 
- shipping_method (partner | 3rd_party) // dibuat enum, tipenya integer 
- courier_id
- courier
- service
- price
- speed_label
- speed
- speed_unit
- city_id

## bidding_submission_shipping_coverages
- bidding_submission_shipping_id
- shipping_address_id

## bidding_submission_proofs
- bidding_submission_spec_id
- shipping_address_id
- shipping (jsonb)
- shipping_method (partner | 3rd_party)
- price
- lead_time
- courier_id
- courier
- service
- shipping_cost
- speed_label
- speed
- speed_unit

## bidding_submission_proof_packs
- bidding_submission_proof_id
- quantity
- weight
- length
- width
- height

## bidding_submission_ranks
- bidding_submission_id
- price_rank
- lead_time_rank
- rating_rank
- submissions_count

## inquiry_supplier_coverages
+ inquiry_shipment_ids (add)
- shipping_address_ids (remove)

## Logic:
- kumpulkan semua bidding_submsission dari sebuah bidding dan di group by spec_set dan di sorting berdasarkan "price", "lead_time", "rating"

```ruby=
# bidding
has_many :quotations
has_many :bidding_submission_specs, through: :quotations
has_many :bidding_submission_groups, through: :bidding_submission_specs
has_many :bidding_submisssions, through: :bidding_submission_groups

# bidding_submission
has_one :bidding_submission_rank

def spec_set
  bidding_submission_group.bidding_submission_spec.spec_set
end

# BiddingSubmissionRankOfficer
groups = biddding.bidding_submissions.group_by(&:spec_set)
groups.each do |spec_set, submissions|
  # [1000, 1000, 1200, 1500]
  # expected rank: 1, 1, 2, 3
  sorted_price     = submissions.map(&:price_each).uniq.sort
  sorted_lead_time = submissions.map(&:lead_time).uniq.sort
  
  submissions.each do |submission|
    rank = submission.bidding_submission_rank || submission.build_bidding_submission_rank
    rank.price_rank        = sorted_price.index(submission.price_each) + 1
    rank.lead_time_rank    = sorted_lead_time.index(submission.lead_time) + 1
    rank.submissions_count = submissions.size
    rank.save!
  end
end
```




---
# Endpoint
## Bidding - New
### URL: `GET /niffler/biddings/inviteds`
### Logic:
```ruby=
# Partner
  has_many :missed_biddings, -> { where('bidding_invitations.status = :status AND bidding_invitations.due_date < :now', status: nil, now: Time.zone.now) }, through: :bidding_invitations, source: :bidding
  has_many :responded_biddings, -> { where.not('bidding_invitations.status': nil }, through: :bidding_invitations, source: :bidding

  has_many :new_biddings, -> { where('bidding_invitations.status': nil) }, through: :bidding_invitations, source: :bidding
  has_many :ongoing_biddings, -> {
    where('bidding_invitations.status': BiddingInvitation.statuses[:accepted])
      .active
  }, through: :bidding_invitations, source: :bidding

  has_many :waiting_biddings, -> {
    joins(:quotation, :inquiry)
      .where('bidding_invitations.status': BiddingInvitation.statuses[:accepted])
      .where('inquiries.status': [Inquiry.statuses[:ready_to_review], Inquiry.statuses[:waiting_approval]])
      .closed
  }, through: :bidding_invitations, source: :bidding

  has_many :history_biddings, -> { 
    missed_biddings.or(
      responded_biddings
        .where.not('inquiries.status': [Inquiry.statuses[:ready_to_review], Inquiry.statuses[:waiting_approval]])
    )
  }, through: :bidding_invitations, source: :bidding
:bidding

``` 

---
## Bidding - Ongoing
### URL: `GET /niffler/biddings/ongoings`

---
## Bidding - Waiting
### URL: `GET /niffler/biddings/waitings`

---
## Bidding - Histories
### URL: `GET /niffler/biddings/histories`

---
## Bidding - Detail
### URL: `GET /niffler/biddings/:id`
### Response
```json=
{
  "id": ID,
  "number": "NUMBER",
  "start_time": 
  "end_time": 
  "round":
  
  "bidding_invitation": {}

  "inquiry": {},
  "organization": {},

  "quotation": {}
}
```


---
## Bidding Invitation - Accept
### URL: `PATCH /niffler/bidding-invitations/:id/approve`
### Logic:
- Approve Invitation
- Create Quotation
- Jika TIDAK Allow Spec Suggestion
  + Create BiddingSubmissionSpec sesuai inquiry spec (suggested: false)

---
## Bidding Invitation - Reject
### URL: `PATCH /niffler/bidding-invitations/:id/reject`

---
## Bidding Submission - Step 1 - Spec
If partner selct "Tidak, gunakan spesifikasi customer"

### URL: `POST /niffler/quotations/:id/bidding-submission-specs`
### Response:
```json=
// array of one bidding submission spec
```
### Logic:
- Create bidding submission specs, set spec_set sesuai inquiry
- Set suggested as FALSE
- Create bidding_submission_groups per spec base on inquiry_supplier_coverages
  - ditambah 1 bidding_submission_groups untuk total quantity (jika multiple shipment)
parnter jakarta
1 jakarta 10
2 jakarta 20
3 bandung 30

inquiry_s_coverage
1 jakarta 30 [1, 2]
2 bandung 30 [3]

bidding-s-group
 coverage [1, 2] 30
 coverage [3] 30
 coverage [1, 2, 3] 60

inquiry_supplier_coverages.each do |coverage|
  bidding_submission_group = Phoenix::BiddingSubmissionGroup.new
  bidding_submission_group.bidding_submission_spec_id = 1
  bidding_submission_group.coverage_ids = coverage.shipping_address_ids
  bidding_submission_group.quantity = coverage.quantity
end

if mulitple
  bidding_submission_group = Phoenix::BiddingSubmissionGroup.new
  bidding_submission_group.bidding_submission_spec_id = 1
  bidding_submission_group.coverage_ids = inquiry_supplier_coverages
  bidding_submission_group.quantity = coverage.sum(:quantity)


---
## Spec Keys (Master)
### URL: `GET /niffler/spec-keys`

---
## Spec Values (Master)
### URL: `GET /niffler/spec-values`

---
## Bidding Submission - Step 1 - Spec Suggestion
### URL: `POST /niffler/quotations/:id/bidding-submission-specs/suggestions`
### Params:
```json=
[{
  
  "id": SUBMISSION_SPEC_ID,
  "position": 1, // tab number
  "suggestion_reason": "not_complete | not_available | other",
  "other_reason": "", // user must add other reason if user select other
  "suggested": true|false,
  "product_id": "",
  "partner_id": "",
  "spec_set": [{
    "spec_key_id": ID,
    "key": "KEY",
    "spec_value_id": ID,
    "custom_value": "", // For custom spec value
    "custom_size": { // For custom size
      "length": X,
      "width": Y
    },
    "position": 1
  }]
}]
```
### Response:
```json=
// array of one or more bidding submission specs
```
### Logic:
- Create atau Update bidding submission specs 
- Set suggested as TRUE
- Create bidding_submission_groups per spec base on inquiry_supplier_coverages
  - ditambah 1 group untuk total quantity (jika multiple shipment)


### Notes:
- Ubah Logic spec_set di inquiry officer

---
## Quotation - Submission Specs
### URL: `GET /niffler/quotations/:id/bidding-submission-specs`
### Response:
```json=
[{
  "id": SUBMISSION_SPEC_ID,
  "spec_set": {},
  "unit": "",
  "suggested": true|false,
  "suggested_reason": "",
  "other_reason": "",
  "product_id": "",
  "partner_id": "",

  "bidding_submission_groups": [{
    "id": ID,
    "bidding_submission_spec_id": SPEC_ID,
    "bidding_submission_spec": {},

    "quantity": 250,
    "coverages": [
      {
        "id": ID,
        "quantity": 100,
        "shipping_address_id": ID,
        "shipping": {},
        // city diambil dari district_id di shipping_address_id (shipping_address.district.city)
        "city": {
          // Jaksel
        }
      }, {
        "id": ID,
        "quantity": 150,
        "shipping_address_id": ID,
        "shipping": {},
        // city diambil dari district_id di shipping_address_id (shipping_address.district.city)
        "city": {
          // Jakbar
        }
      }
    ],
    
    "bidding_submission": {
      "quantity": 250,
      "unit_price": unit_price,
      "lead_time": "",

      "shipping_method": "self_courier|third_party",
      "packing": {
        "quantity": 100,
        "weight": 1000, // gram
        "length": L,
        "width": W,
        "height": H
      },

      "shipping_group": "city | address",
      "shipping_costs": [
        {
          "id": 
          "courier_id": ID,
          "courier": "NAME",
          "service": "reguler",
          "price": 10_000,
          "speed_label": "1 - 2 hari"
          "speed": 2,
          "speed_unit": "day | hour",
          "city_id": ID,

					"shipping_coverages": [1, 2, 3] // isinya inquiry shipment id
        }
      ]
    }
  }],
  "bidding_submission_proof": { "see bidding submission proof" }
}]
```

### Notes
- Case 1: tambah kolom shipping_address_id di tabel bidding_submission_shippings

- Case 2: tambah kolom bidding_submission_shipping_id di tabel bidding_submission_coverages

---
## Quotation - Step 2 - Submission Price
### URL: `POST /niffler/quotations/:id/bidding-submissions`
### Params:
```json=
[
  {
    "id": SUBMISSION_ID, // For update,
    
    "bidding_submission_group_id": GROUP_ID,
    "quantity": 250,
    "unit_price": unit_price,
    "lead_time": "",
    
    "shipping_method": "self_courier|third_party",
    "packing": {
      "quantity": 100,
      "weight": 1000, // gram
      "length": L,
      "width": W,
      "height": H
    },
    
    "shipping_group": "city | address",
    "shipping_costs": [
      {
        "id": 
        "courier_id": ID,
        "courier": "NAME",
        "service": "reguler",
        "price": 10_000,
        "speed_label": "1 - 2 hari"
        "speed": 2,
        "speed_unit": "day | hour",
        "city_id": ID,
        
        // bisa 1 atau banyak -- shipping address id atau inquiry shipment id
        // 1 coverages jika dikirim per Alamat
        // banyak coverages jika dikirim per city
        "shipping_coverages": [1, 2, 3]
      }
    ]
  }
]
```
### Response
```
// array of bidding submissions
```
### Logic:
- tambah column unit_price
- production_cost = unit_price * quantity,
- shipping_cost = sum all bidding_submission_shippings.price ???
- total_price = production_cost + shipping_cost + (ppn10% if partner.pkp)
*harga jual = production_cost
- create bidding_submission_coverages dari groups.coverage_ids

---
???
## Quotation - Insert Bidding Submission Shippings
### URL: `POST /niffler/bidding-submissions/:id/shippings`

### Params:
```json=
{
    "price": 10_000,
    "speed": 2,
    "speed_unit": "day | hour", // default: day
    "shipping_coverage_ids": [1,2,3] // shipping_address_ids, if can choose shipping address
}
```

### Response:
```json=
[
  {
    "id": 
    "courier_id": ID,
    "courier": "NAME",
    "service": "reguler",
    "price": 10_000,
    "speed_label": "1 - 2 hari"
    "speed": 2,
    "speed_unit": "day | hour",
    "city_id": ID,

    // bisa 1 atau banyak -- shipping address id atau inquiry shipment id
    // 1 coverages jika dikirim per Alamat
    // banyak coverages jika dikirim per city
    "shipping_coverage_ids": [1, 2, 3]
  }
]
```

### Logic:
- courier diisi nama partner, service samain
- label = speed + unit

---
## Quotation - Step 3 - Submission Proof
### URL: `POST /niffler/quotations/:id/proofs`
### Params:
```json=
[
  {
    "id": SUBMISSION_PROOF_ID, // For update,
    "price": COST,
    "lead_time": "",
    
    "shipping_method": "self_courier|third_party",
    "packing": {
      "quantity": 100,
      "weight": 1000, // gram
      "length": L,
      "width": W,
      "height": H
    },
    
    "shipping_address_id": 1,
    "courier_id": ID,
    "courier": "NAME",
    "service": "reguler",
    "shipping_cost": 10_000,
    "speed_label": "1 - 2 hari"
    "speed": 2,
    "speed_unit": "day | hour"
  }
]
```

### Response:
```json=
[{"Array of Bidding Submission Proof"}]
```


### Logic:
---
## Quotation - Step 4 - Submission Quotation Deadline
### URL: `POST /niffler/quotations/:id/submit`

### Params:
```json=
{
    "expired_at": ""
}
```

### Response:
```json=
{ "Quotation Detail" }
```

### Logic:
- change status quotation to waiting, change expired_at
- jalankan module calculate rank, Hitung ranking quotation berdasarkan harga, waktu produksi, dan rating
- Kirim quotation ke customer dan trigger notifikasi ke customer. ???
? TODO insight
---
## Quotation - Submission Summary
### URL: `GET /niffler/quotations/:id/bidding-submissions`
### Response:
```json=
[{
  "id": SUBMISSION_SPEC_ID,
  //"spec_set": {},
  "unit": "",
  "suggested": true|false,
  "suggested_reason": "",
  "other_reason": "",
  "product_id": "",
  "partner_id": "",

  "bidding_submissions": [{
    "id": SUBMISSION_ID, // For update,
    "quantity": 100,
    "unit_price": unit_price,
    "production_cost": COST,
    "lead_time": "",
    //
    "shipping_method": "self_courier|third_party",
    "packing": {
      "quantity": 100,
      "weight": 1000, // gram
      "length": L,
      "width": W,
      "height": H
    },
    
    "shipping_group": "city | address",
    "shipping_costs": [
      {
        "id": 
        "courier_id": ID,
        "courier": "NAME",
        "service": "reguler",
        "price": 10_000,
        "speed_label": "1 - 2 hari"
        "speed": 2,
        "speed_unit": "day | hour",
        "city_id": ID,
        
        // bisa 1 atau banyak -- shipping address id atau inquiry shipment id
        // 1 coverages jika dikirim per Alamat
        // banyak coverages jika dikirim per city
        "shipping_coverages": [1, 2, 3]
      }
    ],
    "bidding_submission_proof": {
        "id": SUBMISSION_ID, // For update,
        "price": COST,
        "lead_time": "",

        "shipping_method": "self_courier|third_party",
        "packing": {
          "quantity": 100,
          "weight": 1000, // gram
          "length": L,
          "width": W,
          "height": H
        },

        "shipping_address_id": 1,
        "courier_id": ID,
        "courier": "NAME",
        "service": "reguler",
        "shipping_cost": 10_000,
        "speed_label": "1 - 2 hari"
        "speed": 2,
        "speed_unit": "day | hour"
    },
    "coverages": [
      {
        "id": ID,
        "quantity": 100,
        "shipping_address_id": ID,
        "shipping": {},
        // city diambil dari district_id di shipping_address_id (shipping_address.district.city)
        "city": {
          // Jaksel
        }
      }, {
        "id": ID,
        "quantity": 150,
        "shipping_address_id": ID,
        "shipping": {},
        // city diambil dari district_id di shipping_address_id (shipping_address.district.city)
        "city": {
          // Jakbar
        }
      }
    ],
    
    "submission": {
      // attribute
    }
  }]
}]
```
?? TOTAL shipping

TODO:
- multiple di cutom invite partner for populate inquiry supplier coverages

inquiry_partner_recommendations < SmartInviteOfficer & CustomInviteOfficer(add/remove shipments & invite supplier step 6)

inquiry_supplier_coverages < CloneInquryPartnerRecommendationOfficer

bss_groups < inquiry_supplier_coverages ( create bs_spec )

---
pro - rfq_detail - invite_supplier ?? CustomInviteOfficer & CloneInquiryPartnerRecommendationOfficer & bidding invitation (adjusment) nya gimana?

adjusment bidding_invitation - column partner_id change to org_supplier_id | inquriy_supplier_id


# Table
## bidding_withdraw_reasons
- id
- code
- name       (jsonb: { id: "Males ah" })
- deleted_at

## bidding_withdrawal_reasons
- bidding_invitation_id
- bidding_withdraw_reason_id
- code

## Bidding - Withdraw
### URL: `PATCH /niffler/biddings/:id/withdraw`
### Params:
```json=
{
  
}
```
### Response:
```json=
/// bidding detail
```
### Logic:
- Update bidding invitation status to withdraw
- Update quotation status to cancelled
