# [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