add sets up

This commit is contained in:
mr 2025-06-20 12:10:36 +02:00
parent 583ca2fbac
commit 29b192211d
22 changed files with 136 additions and 131 deletions

View File

@ -26,7 +26,20 @@ type Bill struct {
Total float64 `json:"total" bson:"total" validate:"required"` Total float64 `json:"total" bson:"total" validate:"required"`
} }
func DraftBill(order *order.Order, request *tools.APIRequest) (*Bill, error) { func GenerateBill(order *order.Order, request *tools.APIRequest) (*Bill, error) {
// hhmmm : should get... the loop.
return &Bill{
AbstractObject: utils.AbstractObject{
Name: "bill_" + request.PeerID + "_" + time.Now().UTC().Format("2006-01-02T15:04:05"),
IsDraft: false,
},
OrderID: order.UUID,
Status: enum.PENDING,
// SubOrders: peerOrders,
}, nil
}
func DraftFirstBill(order *order.Order, request *tools.APIRequest) (*Bill, error) {
peers := map[string][]*PeerItemOrder{} peers := map[string][]*PeerItemOrder{}
for _, p := range order.Purchases { for _, p := range order.Purchases {
// TODO : if once // TODO : if once

View File

@ -0,0 +1,2 @@
# Billing process
Scheduler process a drafted order + a first bill corresponding to every once buying.

View File

@ -12,6 +12,7 @@ type PricedItemITF interface {
IsPurchasable() bool IsPurchasable() bool
IsBooked() bool IsBooked() bool
GetCreatorID() string GetCreatorID() string
SelectPricing() PricingProfileITF
GetLocationStart() *time.Time GetLocationStart() *time.Time
SetLocationStart(start time.Time) SetLocationStart(start time.Time)
SetLocationEnd(end time.Time) SetLocationEnd(end time.Time)

View File

@ -5,10 +5,11 @@ import (
) )
type PricingProfileITF interface { type PricingProfileITF interface {
GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error)
IsPurchasable() bool
IsBooked() bool IsBooked() bool
IsPurchasable() bool
GetPurchase() BuyingStrategy
GetOverrideStrategyValue() int GetOverrideStrategyValue() int
GetPrice(quantity float64, val float64, start time.Time, end time.Time, params ...string) (float64, error)
} }
type RefundType int type RefundType int

View File

@ -5,6 +5,7 @@ import (
"cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/booking"
"cloud.o-forge.io/core/oc-lib/models/common/enum" "cloud.o-forge.io/core/oc-lib/models/common/enum"
"cloud.o-forge.io/core/oc-lib/models/common/pricing"
"cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource" "cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource"
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
@ -17,9 +18,11 @@ import (
type Order struct { type Order struct {
utils.AbstractObject utils.AbstractObject
ExecutionsID string `json:"executions_id" bson:"executions_id" validate:"required"` ExecutionsID string `json:"executions_id" bson:"executions_id" validate:"required"`
Status enum.CompletionStatus `json:"status" bson:"status" default:"0"`
Purchases []*purchase_resource.PurchaseResource `json:"purchases" bson:"purchases"` Purchases []*purchase_resource.PurchaseResource `json:"purchases" bson:"purchases"`
Bookings []*booking.Booking `json:"bookings" bson:"bookings"` Bookings []*booking.Booking `json:"bookings" bson:"bookings"`
Status enum.CompletionStatus `json:"status" bson:"status" default:"0"`
Billing map[pricing.BillingStrategy][]*booking.Booking `json:"billing" bson:"billing"`
} }
func (r *Order) StoreDraftDefault() { func (r *Order) StoreDraftDefault() {

View File

@ -30,12 +30,11 @@ func (r *ComputeResource) GetType() string {
return tools.COMPUTE_RESOURCE.String() return tools.COMPUTE_RESOURCE.String()
} }
func (abs *ComputeResource) ConvertToPricedResource( func (abs *ComputeResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF {
if t != tools.COMPUTE_RESOURCE { if t != tools.COMPUTE_RESOURCE {
return nil return nil
} }
p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request, buyingStrategy, pricingStrategy) p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource) priced := p.(*PricedResource)
return &PricedComputeResource{ return &PricedComputeResource{
PricedResource: *priced, PricedResource: *priced,
@ -84,6 +83,10 @@ func (p *ComputeResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
} }
func (p *ComputeResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *ComputeResourcePricingProfile) IsBooked() bool { func (p *ComputeResourcePricingProfile) IsBooked() bool {
if p.Pricing.BuyingStrategy == pricing.PERMANENT { if p.Pricing.BuyingStrategy == pricing.PERMANENT {
p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION p.Pricing.BuyingStrategy = pricing.SUBSCRIPTION
@ -159,10 +162,7 @@ func (r *PricedComputeResource) GetPrice() (float64, error) {
r.UsageEnd = &add r.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
if len(r.PricingProfiles) == 0 { return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID)
return 0, errors.New("pricing profile must be set on Priced Compute" + r.ResourceID)
}
r.SelectedPricing = r.PricingProfiles[0]
} }
pricing := r.SelectedPricing pricing := r.SelectedPricing
price := float64(0) price := float64(0)

View File

@ -37,12 +37,11 @@ func (r *DataResource) GetType() string {
return tools.DATA_RESOURCE.String() return tools.DATA_RESOURCE.String()
} }
func (abs *DataResource) ConvertToPricedResource( func (abs *DataResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF {
if t != tools.DATA_RESOURCE { if t != tools.DATA_RESOURCE {
return nil return nil
} }
p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request, buyingStrategy, pricingStrategy) p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource) priced := p.(*PricedResource)
return &PricedDataResource{ return &PricedDataResource{
PricedResource: *priced, PricedResource: *priced,
@ -137,6 +136,10 @@ func (p *DataResourcePricingProfile) GetPrice(amountOfData float64, explicitDura
return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end) return p.Pricing.GetPrice(amountOfData, explicitDuration, start, &end)
} }
func (p *DataResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *DataResourcePricingProfile) IsPurchasable() bool { func (p *DataResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
} }
@ -166,10 +169,7 @@ func (r *PricedDataResource) GetPrice() (float64, error) {
r.UsageEnd = &add r.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
if len(r.PricingProfiles) == 0 { return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID)
return 0, errors.New("pricing profile must be set on Priced Data" + r.ResourceID)
}
r.SelectedPricing = r.PricingProfiles[0]
} }
pricing := r.SelectedPricing pricing := r.SelectedPricing
var err error var err error

View File

@ -9,9 +9,9 @@ import (
type ResourceInterface interface { type ResourceInterface interface {
utils.DBObject utils.DBObject
Trim() Trim()
ConvertToPricedResource(t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF
GetType() string GetType() string
GetSelectedInstance() utils.DBObject GetSelectedInstance() ResourceInstanceITF
ClearEnv() utils.DBObject ClearEnv() utils.DBObject
SetAllowedInstances(request *tools.APIRequest) SetAllowedInstances(request *tools.APIRequest)
} }
@ -22,15 +22,15 @@ type ResourceInstanceITF interface {
GetName() string GetName() string
StoreDraftDefault() StoreDraftDefault()
ClearEnv() ClearEnv()
GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF GetProfile() pricing.PricingProfileITF
GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string) GetPeerGroups() ([]ResourcePartnerITF, []map[string][]string)
ClearPeerGroups() ClearPeerGroups()
GetSelectedPartnership() ResourcePartnerITF
GetPartnerships(peerID string, groups []string) []ResourcePartnerITF GetPartnerships(peerID string, groups []string) []ResourcePartnerITF
} }
type ResourcePartnerITF interface { type ResourcePartnerITF interface {
GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF
GetPeerGroups() map[string][]string GetPeerGroups() map[string][]string
ClearPeerGroups() ClearPeerGroups()
} }

View File

@ -10,17 +10,20 @@ import (
) )
type PricedResource struct { type PricedResource struct {
Name string `json:"name,omitempty" bson:"name,omitempty"` Name string `json:"name,omitempty" bson:"name,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo,omitempty"` Logo string `json:"logo,omitempty" bson:"logo,omitempty"`
InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"` InstancesRefs map[string]string `json:"instances_refs,omitempty" bson:"instances_refs,omitempty"`
PricingProfiles []pricing.PricingProfileITF `json:"pricing_profiles,omitempty" bson:"pricing_profiles,omitempty"` SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"` ExplicitBookingDurationS float64 `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"`
ExplicitBookingDurationS float64 `json:"explicit_location_duration_s,omitempty" bson:"explicit_location_duration_s,omitempty"` UsageStart *time.Time `json:"start,omitempty" bson:"start,omitempty"`
UsageStart *time.Time `json:"start,omitempty" bson:"start,omitempty"` UsageEnd *time.Time `json:"end,omitempty" bson:"end,omitempty"`
UsageEnd *time.Time `json:"end,omitempty" bson:"end,omitempty"` CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"`
CreatorID string `json:"peer_id,omitempty" bson:"peer_id,omitempty"` ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"`
ResourceID string `json:"resource_id,omitempty" bson:"resource_id,omitempty"` ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"`
ResourceType tools.DataType `json:"resource_type,omitempty" bson:"resource_type,omitempty"` }
func (abs *PricedResource) SelectPricing() pricing.PricingProfileITF {
return abs.SelectedPricing
} }
func (abs *PricedResource) GetID() string { func (abs *PricedResource) GetID() string {
@ -90,10 +93,7 @@ func (r *PricedResource) GetPrice() (float64, error) {
r.UsageEnd = &add r.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
if len(r.PricingProfiles) == 0 { return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID)
return 0, errors.New("pricing profile must be set on Priced Resource " + r.ResourceID)
}
r.SelectedPricing = r.PricingProfiles[0]
} }
pricing := r.SelectedPricing pricing := r.SelectedPricing
return pricing.GetPrice(1, 0, *r.UsageStart, *r.UsageEnd) return pricing.GetPrice(1, 0, *r.UsageStart, *r.UsageEnd)

View File

@ -85,6 +85,10 @@ func (p *ProcessingResourcePricingProfile) IsBooked() bool {
return p.Pricing.BuyingStrategy != pricing.PERMANENT return p.Pricing.BuyingStrategy != pricing.PERMANENT
} }
func (p *ProcessingResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) { func (p *ProcessingResourcePricingProfile) GetPrice(amountOfData float64, val float64, start time.Time, end time.Time, params ...string) (float64, error) {
return p.Pricing.GetPrice(amountOfData, val, start, &end) return p.Pricing.GetPrice(amountOfData, val, start, &end)
} }

View File

@ -24,7 +24,7 @@ type AbstractResource struct {
SelectedInstanceIndex *int `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance SelectedInstanceIndex *int `json:"selected_instance_index,omitempty" bson:"selected_instance_index,omitempty"` // SelectedInstance is the selected instance
} }
func (r *AbstractResource) GetSelectedInstance() utils.DBObject { func (r *AbstractResource) GetSelectedInstance() ResourceInstanceITF {
return nil return nil
} }
@ -52,13 +52,19 @@ type AbstractInstanciatedResource[T ResourceInstanceITF] struct {
Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource Instances []T `json:"instances,omitempty" bson:"instances,omitempty"` // Bill is the bill of the resource // Bill is the bill of the resource
} }
func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource( func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF {
instances := map[string]string{} instances := map[string]string{}
profiles := []pricing.PricingProfileITF{} profiles := []pricing.PricingProfileITF{}
for _, instance := range abs.Instances { for _, instance := range abs.Instances {
instances[instance.GetID()] = instance.GetName() instances[instance.GetID()] = instance.GetName()
profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups, buyingStrategy, pricingStrategy) profiles = instance.GetPricingsProfiles(request.PeerID, request.Groups)
}
var profile pricing.PricingProfileITF
if t := abs.GetSelectedInstance(); t != nil {
profile = t.GetProfile()
}
if profile == nil && len(profiles) > 0 {
profile = profiles[0]
} }
return &PricedResource{ return &PricedResource{
Name: abs.Name, Name: abs.Name,
@ -66,7 +72,7 @@ func (abs *AbstractInstanciatedResource[T]) ConvertToPricedResource(
ResourceID: abs.UUID, ResourceID: abs.UUID,
ResourceType: t, ResourceType: t,
InstancesRefs: instances, InstancesRefs: instances,
PricingProfiles: profiles, SelectedPricing: profile,
CreatorID: abs.CreatorID, CreatorID: abs.CreatorID,
} }
} }
@ -78,7 +84,7 @@ func (abs *AbstractInstanciatedResource[T]) ClearEnv() utils.DBObject {
return abs return abs
} }
func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() utils.DBObject { func (r *AbstractInstanciatedResource[T]) GetSelectedInstance() ResourceInstanceITF {
if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex { if r.SelectedInstanceIndex != nil && len(r.Instances) > *r.SelectedInstanceIndex {
return r.Instances[*r.SelectedInstanceIndex] return r.Instances[*r.SelectedInstanceIndex]
} }
@ -144,24 +150,14 @@ type Credentials struct {
type ResourceInstance[T ResourcePartnerITF] struct { type ResourceInstance[T ResourcePartnerITF] struct {
utils.AbstractObject utils.AbstractObject
Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"` Location GeoPoint `json:"location,omitempty" bson:"location,omitempty"`
Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"` Country countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"` AccessProtocol string `json:"access_protocol,omitempty" bson:"access_protocol,omitempty"`
Env []models.Param `json:"env,omitempty" bson:"env,omitempty"` Env []models.Param `json:"env,omitempty" bson:"env,omitempty"`
Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"` Inputs []models.Param `json:"inputs,omitempty" bson:"inputs,omitempty"`
Outputs []models.Param `json:"outputs,omitempty" bson:"outputs,omitempty"` Outputs []models.Param `json:"outputs,omitempty" bson:"outputs,omitempty"`
SelectedPartnershipIndex *int `json:"selected_partnership_index,omitempty" bson:"selected_partnership_index,omitempty"` // SelectedInstance is the selected instance SelectedPricing pricing.PricingProfileITF `json:"selected_pricing,omitempty" bson:"selected_pricing,omitempty"`
Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"` Partnerships []T `json:"partnerships,omitempty" bson:"partnerships,omitempty"`
}
func (r *ResourceInstance[T]) GetSelectedPartnership() ResourcePartnerITF {
if r.SelectedPartnershipIndex != nil && len(r.Partnerships) > *r.SelectedPartnershipIndex {
return r.Partnerships[*r.SelectedPartnershipIndex]
}
if len(r.Partnerships) > 0 {
return r.Partnerships[0]
}
return nil
} }
func (ri *ResourceInstance[T]) ClearEnv() { func (ri *ResourceInstance[T]) ClearEnv() {
@ -170,6 +166,10 @@ func (ri *ResourceInstance[T]) ClearEnv() {
ri.Outputs = []models.Param{} ri.Outputs = []models.Param{}
} }
func (ri *ResourceInstance[T]) GetProfile() pricing.PricingProfileITF {
return ri.SelectedPricing
}
func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF { func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) []ResourcePartnerITF {
partners := []ResourcePartnerITF{} partners := []ResourcePartnerITF{}
for _, p := range ri.Partnerships { for _, p := range ri.Partnerships {
@ -184,10 +184,10 @@ func (ri *ResourceInstance[T]) GetPartnerships(peerID string, groups []string) [
return partners return partners
} }
func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF { func (ri *ResourceInstance[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
pricings := []pricing.PricingProfileITF{} pricings := []pricing.PricingProfileITF{}
for _, p := range ri.Partnerships { for _, p := range ri.Partnerships {
pricings = append(pricings, p.GetPricingsProfiles(peerID, groups, buyingStrategy, pricingStrategy)...) pricings = append(pricings, p.GetPricingsProfiles(peerID, groups)...)
} }
return pricings return pricings
} }
@ -222,16 +222,16 @@ note : il faut rajouté - une notion de facturation
Une order est l'ensemble de la commande... un booking une réservation, une purchase un acte d'achat. Une order est l'ensemble de la commande... un booking une réservation, une purchase un acte d'achat.
Une bill (facture) représente alors... l'emission d'une facture à un instant T en but d'être honorée, envoyée ... etc. Une bill (facture) représente alors... l'emission d'une facture à un instant T en but d'être honorée, envoyée ... etc.
*/ */
func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string, buyingStrategy int, pricingStrategy int) []pricing.PricingProfileITF { func (ri *ResourcePartnerShip[T]) GetPricingsProfiles(peerID string, groups []string) []pricing.PricingProfileITF {
profiles := []pricing.PricingProfileITF{} profiles := []pricing.PricingProfileITF{}
if ri.PeerGroups[peerID] == nil { if ri.PeerGroups[peerID] == nil {
return profiles return profiles
} }
for _, p := range ri.PeerGroups[peerID] { for _, p := range ri.PeerGroups[peerID] {
if slices.Contains(groups, p) || slices.Contains(groups, "*") { if slices.Contains(groups, p) || slices.Contains(groups, "*") {
for buyingS, ri := range ri.PricingProfiles { for _, ri := range ri.PricingProfiles {
if _, ok := ri[pricingStrategy]; ok && buyingS == buyingStrategy { for _, i := range ri {
profiles = append(profiles, ri[pricingStrategy]) profiles = append(profiles, i)
} }
} }
return profiles return profiles

View File

@ -30,12 +30,11 @@ func (r *StorageResource) GetType() string {
return tools.STORAGE_RESOURCE.String() return tools.STORAGE_RESOURCE.String()
} }
func (abs *StorageResource) ConvertToPricedResource( func (abs *StorageResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF {
if t != tools.STORAGE_RESOURCE { if t != tools.STORAGE_RESOURCE {
return nil return nil
} }
p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request, buyingStrategy, pricingStrategy) p := abs.AbstractInstanciatedResource.ConvertToPricedResource(t, request)
priced := p.(*PricedResource) priced := p.(*PricedResource)
return &PricedStorageResource{ return &PricedStorageResource{
PricedResource: *priced, PricedResource: *priced,
@ -153,6 +152,10 @@ type StorageResourcePricingProfile struct {
pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time pricing.ExploitPricingProfile[StorageResourcePricingStrategy] // ExploitPricingProfile is the pricing profile of a storage it means that we exploit the resource for an amount of continuous time
} }
func (p *StorageResourcePricingProfile) GetPurchase() pricing.BuyingStrategy {
return p.Pricing.BuyingStrategy
}
func (p *StorageResourcePricingProfile) IsPurchasable() bool { func (p *StorageResourcePricingProfile) IsPurchasable() bool {
return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION return p.Pricing.BuyingStrategy != pricing.UNDEFINED_SUBSCRIPTION
} }
@ -188,10 +191,7 @@ func (r *PricedStorageResource) GetPrice() (float64, error) {
r.UsageEnd = &add r.UsageEnd = &add
} }
if r.SelectedPricing == nil { if r.SelectedPricing == nil {
if len(r.PricingProfiles) == 0 { return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID)
return 0, errors.New("pricing profile must be set on Priced Storage" + r.ResourceID)
}
r.SelectedPricing = r.PricingProfiles[0]
} }
pricing := r.SelectedPricing pricing := r.SelectedPricing
var err error var err error

View File

@ -30,7 +30,7 @@ func TestComputeResource_ConvertToPricedResource(t *testing.T) {
cr := &resources.ComputeResource{} cr := &resources.ComputeResource{}
cr.UUID = "comp123" cr.UUID = "comp123"
cr.AbstractInstanciatedResource.UUID = cr.UUID cr.AbstractInstanciatedResource.UUID = cr.UUID
result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req, 0, 0) result := cr.ConvertToPricedResource(tools.COMPUTE_RESOURCE, req)
assert.NotNil(t, result) assert.NotNil(t, result)
assert.IsType(t, &resources.PricedComputeResource{}, result) assert.IsType(t, &resources.PricedComputeResource{}, result)
} }
@ -62,20 +62,9 @@ func TestComputeResourcePricingProfile_GetPrice_InvalidParams(t *testing.T) {
func TestPricedComputeResource_GetPrice(t *testing.T) { func TestPricedComputeResource_GetPrice(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(1 * time.Hour) end := start.Add(1 * time.Hour)
profile := &resources.ComputeResourcePricingProfile{
CPUsPrices: map[string]float64{"Xeon": 1.0},
GPUsPrices: map[string]float64{"Tesla": 2.0},
RAMPrice: 0.5,
ExploitPricingProfile: pricing.ExploitPricingProfile[pricing.TimePricingStrategy]{
AccessPricingProfile: pricing.AccessPricingProfile[pricing.TimePricingStrategy]{
Pricing: pricing.PricingStrategy[pricing.TimePricingStrategy]{Price: 1.0},
},
},
}
r := resources.PricedComputeResource{ r := resources.PricedComputeResource{
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
ResourceID: "comp456", ResourceID: "comp456",
PricingProfiles: []pricing.PricingProfileITF{profile},
UsageStart: &start, UsageStart: &start,
UsageEnd: &end, UsageEnd: &end,
ExplicitBookingDurationS: 3600, ExplicitBookingDurationS: 3600,

View File

@ -27,10 +27,10 @@ func TestDataResource_GetAccessor(t *testing.T) {
func TestDataResource_ConvertToPricedResource(t *testing.T) { func TestDataResource_ConvertToPricedResource(t *testing.T) {
d := &resources.DataResource{} d := &resources.DataResource{}
d.UUID = "123" d.UUID = "123"
res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{}, 0, 0) res := d.ConvertToPricedResource(tools.DATA_RESOURCE, &tools.APIRequest{})
assert.IsType(t, &resources.PricedDataResource{}, res) assert.IsType(t, &resources.PricedDataResource{}, res)
nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{}, 0, 0) nilRes := d.ConvertToPricedResource(tools.PROCESSING_RESOURCE, &tools.APIRequest{})
assert.Nil(t, nilRes) assert.Nil(t, nilRes)
} }
@ -94,9 +94,6 @@ func TestPricedDataResource_GetPrice(t *testing.T) {
PricedResource: resources.PricedResource{ PricedResource: resources.PricedResource{
UsageStart: &now, UsageStart: &now,
UsageEnd: &later, UsageEnd: &later,
PricingProfiles: []pricing.PricingProfileITF{
pricingProfile,
},
}, },
} }

View File

@ -101,11 +101,9 @@ func TestGetPrice(t *testing.T) {
t.Run("uses first profile if selected is nil", func(t *testing.T) { t.Run("uses first profile if selected is nil", func(t *testing.T) {
start := time.Now() start := time.Now()
end := start.Add(30 * time.Minute) end := start.Add(30 * time.Minute)
mock := &MockPricingProfile{ReturnCost: 42.0}
r := &resources.PricedResource{ r := &resources.PricedResource{
PricingProfiles: []pricing.PricingProfileITF{mock}, UsageStart: &start,
UsageStart: &start, UsageEnd: &end,
UsageEnd: &end,
} }
price, err := r.GetPrice() price, err := r.GetPrice()
require.NoError(t, err) require.NoError(t, err)

View File

@ -20,7 +20,7 @@ func (m *MockInstance) GetID() string { return m.ID }
func (m *MockInstance) GetName() string { return m.Name } func (m *MockInstance) GetName() string { return m.Name }
func (m *MockInstance) ClearEnv() {} func (m *MockInstance) ClearEnv() {}
func (m *MockInstance) ClearPeerGroups() {} func (m *MockInstance) ClearPeerGroups() {}
func (m *MockInstance) GetPricingsProfiles(string, []string, int, int) []pricing.PricingProfileITF { func (m *MockInstance) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF {
return nil return nil
} }
func (m *MockInstance) GetPeerGroups() ([]resources.ResourcePartnerITF, []map[string][]string) { func (m *MockInstance) GetPeerGroups() ([]resources.ResourcePartnerITF, []map[string][]string) {
@ -37,7 +37,7 @@ func (m *MockPartner) GetPeerGroups() map[string][]string {
return m.groups return m.groups
} }
func (m *MockPartner) ClearPeerGroups() {} func (m *MockPartner) ClearPeerGroups() {}
func (m *MockPartner) GetPricingsProfiles(string, []string, int, int) []pricing.PricingProfileITF { func (m *MockPartner) GetPricingsProfiles(string, []string) []pricing.PricingProfileITF {
return nil return nil
} }

View File

@ -26,14 +26,14 @@ func TestStorageResource_ConvertToPricedResource_ValidType(t *testing.T) {
res := &resources.StorageResource{} res := &resources.StorageResource{}
res.AbstractInstanciatedResource.CreatorID = "creator" res.AbstractInstanciatedResource.CreatorID = "creator"
res.AbstractInstanciatedResource.UUID = "res-id" res.AbstractInstanciatedResource.UUID = "res-id"
priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{}, 0, 0) priced := res.ConvertToPricedResource(tools.STORAGE_RESOURCE, &tools.APIRequest{})
assert.NotNil(t, priced) assert.NotNil(t, priced)
assert.IsType(t, &resources.PricedStorageResource{}, priced) assert.IsType(t, &resources.PricedStorageResource{}, priced)
} }
func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) { func TestStorageResource_ConvertToPricedResource_InvalidType(t *testing.T) {
res := &resources.StorageResource{} res := &resources.StorageResource{}
priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{}, 0, 0) priced := res.ConvertToPricedResource(tools.COMPUTE_RESOURCE, &tools.APIRequest{})
assert.Nil(t, priced) assert.Nil(t, priced)
} }

View File

@ -32,7 +32,7 @@ func TestWorkflowResource_ConvertToPricedResource(t *testing.T) {
Groups: []string{"group1"}, Groups: []string{"group1"},
} }
pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req, 0, 0) pr := w.ConvertToPricedResource(tools.WORKFLOW_RESOURCE, req)
assert.Equal(t, "creator-id", pr.GetCreatorID()) assert.Equal(t, "creator-id", pr.GetCreatorID())
assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType()) assert.Equal(t, tools.WORKFLOW_RESOURCE, pr.GetType())
} }

View File

@ -16,7 +16,7 @@ type WorkflowResource struct {
} }
func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor { func (d *WorkflowResource) GetAccessor(request *tools.APIRequest) utils.Accessor {
return NewAccessor[*ComputeResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} }) return NewAccessor[*WorkflowResource](tools.WORKFLOW_RESOURCE, request, func() utils.DBObject { return &WorkflowResource{} })
} }
func (r *WorkflowResource) GetType() string { func (r *WorkflowResource) GetType() string {
@ -34,8 +34,7 @@ func (w *WorkflowResource) SetAllowedInstances(request *tools.APIRequest) {
/* EMPTY */ /* EMPTY */
} }
func (w *WorkflowResource) ConvertToPricedResource( func (w *WorkflowResource) ConvertToPricedResource(t tools.DataType, request *tools.APIRequest) pricing.PricedItemITF {
t tools.DataType, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) pricing.PricedItemITF {
return &PricedResource{ return &PricedResource{
Name: w.Name, Name: w.Name,
Logo: w.Logo, Logo: w.Logo,

View File

@ -55,7 +55,7 @@ func (wf *Graph) IsWorkflow(item GraphItem) bool {
} }
func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface, func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, processings []*resources.ProcessingResource, resource resources.ResourceInterface,
f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) (float64, float64) { f func(GraphItem) resources.ResourceInterface, request *tools.APIRequest) (float64, float64) {
nearestStart := float64(10000000000) nearestStart := float64(10000000000)
oneIsInfinite := false oneIsInfinite := false
longestDuration := float64(0) longestDuration := float64(0)
@ -67,7 +67,7 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce
} else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute } else if link.Source.ID == processing.GetID() && f(g.Items[link.Source.ID]) != nil && f(g.Items[link.Source.ID]).GetID() == resource.GetID() { // if the source is the processing and the destination is not a compute
source = link.Destination.ID source = link.Destination.ID
} }
priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request, buyingStrategy, pricingStrategy) priced := processing.ConvertToPricedResource(tools.PROCESSING_RESOURCE, request)
if source != "" { if source != "" {
if priced.GetLocationStart() != nil { if priced.GetLocationStart() != nil {
near := float64(priced.GetLocationStart().Sub(start).Seconds()) near := float64(priced.GetLocationStart().Sub(start).Seconds())
@ -96,7 +96,7 @@ func (g *Graph) GetAverageTimeRelatedToProcessingActivity(start time.Time, proce
/* /*
* GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing * GetAverageTimeBeforeStart is a function that returns the average time before the start of a processing
*/ */
func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) float64 { func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingID string, request *tools.APIRequest) float64 {
currents := []float64{} // list of current time currents := []float64{} // list of current time
for _, link := range g.Links { // for each link for _, link := range g.Links { // for each link
var source string // source is the source of the link var source string // source is the source of the link
@ -112,13 +112,13 @@ func (g *Graph) GetAverageTimeProcessingBeforeStart(average float64, processingI
if r == nil { // if item is nil, continue if r == nil { // if item is nil, continue
continue continue
} }
priced := r.ConvertToPricedResource(dt, request, buyingStrategy, pricingStrategy) priced := r.ConvertToPricedResource(dt, request)
current := priced.GetExplicitDurationInS() // get the explicit duration of the item current := priced.GetExplicitDurationInS() // get the explicit duration of the item
if current < 0 { // if current is negative, its means that duration of a before could be infinite continue if current < 0 { // if current is negative, its means that duration of a before could be infinite continue
return current return current
} }
current += g.GetAverageTimeProcessingBeforeStart(current, source, request, buyingStrategy, pricingStrategy) // get the average time before start of the source current += g.GetAverageTimeProcessingBeforeStart(current, source, request) // get the average time before start of the source
currents = append(currents, current) // append the current to the currents currents = append(currents, current) // append the current to the currents
} }
var max float64 // get the max time to wait dependancies to finish var max float64 // get the max time to wait dependancies to finish
for _, current := range currents { for _, current := range currents {

View File

@ -83,7 +83,7 @@ func (w *Workflow) GetPricedItem(
for _, item := range w.Graph.Items { for _, item := range w.Graph.Items {
if f(item) { if f(item) {
dt, res := item.GetResource() dt, res := item.GetResource()
ord := res.ConvertToPricedResource(dt, request, buyingStrategy, pricingStrategy) ord := res.ConvertToPricedResource(dt, request)
list_datas[res.GetID()] = ord list_datas[res.GetID()] = ord
} }
} }
@ -166,12 +166,11 @@ func (wfa *Workflow) CheckBooking(caller *tools.HTTPCaller) (bool, error) {
return true, nil return true, nil
} }
func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest, buyingStrategy int, pricingStrategy int) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) { func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIRequest) (float64, map[tools.DataType]map[string]pricing.PricedItemITF, *Workflow, error) {
priceds := map[tools.DataType]map[string]pricing.PricedItemITF{} priceds := map[tools.DataType]map[string]pricing.PricedItemITF{}
ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, ps, priceds, err := plan[*resources.ProcessingResource](tools.PROCESSING_RESOURCE, wf, priceds, request, wf.Graph.IsProcessing,
buyingStrategy, pricingStrategy, wf.Graph.IsProcessing,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request, buyingStrategy, pricingStrategy)) * time.Second), priced.GetExplicitDurationInS() return start.Add(time.Duration(wf.Graph.GetAverageTimeProcessingBeforeStart(0, res.GetID(), request)) * time.Second), priced.GetExplicitDurationInS()
}, func(started time.Time, duration float64) *time.Time { }, func(started time.Time, duration float64) *time.Time {
s := started.Add(time.Duration(duration)) s := started.Add(time.Duration(duration))
return &s return &s
@ -179,7 +178,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR
if err != nil { if err != nil {
return 0, priceds, nil, err return 0, priceds, nil, err
} }
if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request, buyingStrategy, pricingStrategy, if _, priceds, err = plan[resources.ResourceInterface](tools.DATA_RESOURCE, wf, priceds, request,
wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { wf.Graph.IsData, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
return start, 0 return start, 0
}, func(started time.Time, duration float64) *time.Time { }, func(started time.Time, duration float64) *time.Time {
@ -188,14 +187,14 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR
return 0, priceds, nil, err return 0, priceds, nil, err
} }
for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} { for k, f := range map[tools.DataType]func(graph.GraphItem) bool{tools.STORAGE_RESOURCE: wf.Graph.IsStorage, tools.COMPUTE_RESOURCE: wf.Graph.IsCompute} {
if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request, buyingStrategy, pricingStrategy, if _, priceds, err = plan[resources.ResourceInterface](k, wf, priceds, request,
f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { f, func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) { nearestStart, longestDuration := wf.Graph.GetAverageTimeRelatedToProcessingActivity(start, ps, res, func(i graph.GraphItem) (r resources.ResourceInterface) {
if f(i) { if f(i) {
_, r = i.GetResource() _, r = i.GetResource()
} }
return r return r
}, request, buyingStrategy, pricingStrategy) }, request)
return start.Add(time.Duration(nearestStart) * time.Second), longestDuration return start.Add(time.Duration(nearestStart) * time.Second), longestDuration
}, func(started time.Time, duration float64) *time.Time { }, func(started time.Time, duration float64) *time.Time {
s := started.Add(time.Duration(duration)) s := started.Add(time.Duration(duration))
@ -205,8 +204,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR
} }
} }
longest := common.GetPlannerLongestTime(end, priceds, request) longest := common.GetPlannerLongestTime(end, priceds, request)
if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, if _, priceds, err = plan[resources.ResourceInterface](tools.WORKFLOW_RESOURCE, wf, priceds, request, wf.Graph.IsWorkflow,
buyingStrategy, pricingStrategy, wf.Graph.IsWorkflow,
func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) { func(res resources.ResourceInterface, priced pricing.PricedItemITF) (time.Time, float64) {
start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second) start := start.Add(time.Duration(common.GetPlannerNearestStart(start, priceds, request)) * time.Second)
longest := float64(-1) longest := float64(-1)
@ -214,7 +212,7 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR
if code != 200 || err != nil { if code != 200 || err != nil {
return start, longest return start, longest
} }
if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request, buyingStrategy, pricingStrategy); err != nil { if neoLongest, _, _, err := r.(*Workflow).Planify(start, end, request); err != nil {
return start, longest return start, longest
} else if neoLongest > longest { } else if neoLongest > longest {
longest = neoLongest longest = neoLongest
@ -231,7 +229,6 @@ func (wf *Workflow) Planify(start time.Time, end *time.Time, request *tools.APIR
func plan[T resources.ResourceInterface]( func plan[T resources.ResourceInterface](
dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest, dt tools.DataType, wf *Workflow, priceds map[tools.DataType]map[string]pricing.PricedItemITF, request *tools.APIRequest,
buyingStrategy int, pricingStrategy int,
f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) { f func(graph.GraphItem) bool, start func(resources.ResourceInterface, pricing.PricedItemITF) (time.Time, float64), end func(time.Time, float64) *time.Time) ([]T, map[tools.DataType]map[string]pricing.PricedItemITF, error) {
resources := []T{} resources := []T{}
for _, item := range wf.GetGraphItems(f) { for _, item := range wf.GetGraphItems(f) {
@ -242,7 +239,10 @@ func plan[T resources.ResourceInterface](
if realItem == nil { if realItem == nil {
return resources, priceds, errors.New("could not load the processing resource") return resources, priceds, errors.New("could not load the processing resource")
} }
priced := realItem.ConvertToPricedResource(dt, request, buyingStrategy, pricingStrategy) priced := realItem.ConvertToPricedResource(dt, request)
if priced.SelectPricing() == nil {
return resources, priceds, errors.New("no pricings are selected... can't proceed")
}
started, duration := start(realItem, priced) started, duration := start(realItem, priced)
priced.SetLocationStart(started) priced.SetLocationStart(started)
if duration >= 0 { if duration >= 0 {

View File

@ -37,8 +37,6 @@ type WorkflowSchedule struct {
DurationS float64 `json:"duration_s" default:"-1"` // End is the end time of the schedule DurationS float64 `json:"duration_s" default:"-1"` // End is the end time of the schedule
Cron string `json:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task Cron string `json:"cron,omitempty"` // here the cron format : ss mm hh dd MM dw task
SelectedBuyingStrategy pricing.BuyingStrategy `json:"selected_buying_strategy"`
SelectedPricingStrategy int `json:"selected_pricing_strategy"`
SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"` SelectedBillingStrategy pricing.BillingStrategy `json:"selected_billing_strategy"`
} }
@ -70,7 +68,7 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest
return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, errors.New("could not load the workflow with id: " + err.Error()) return false, nil, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, errors.New("could not load the workflow with id: " + err.Error())
} }
wf := res.(*workflow.Workflow) wf := res.(*workflow.Workflow)
longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, request, int(ws.SelectedBuyingStrategy), ws.SelectedPricingStrategy) longest, priceds, wf, err := wf.Planify(ws.Start, ws.End, request)
if err != nil { if err != nil {
return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err return false, wf, []*WorkflowExecution{}, []*purchase_resource.PurchaseResource{}, []*booking.Booking{}, err
} }
@ -90,10 +88,6 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest
bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...) bookings = append(bookings, exec.Book(ws.UUID, wfID, priceds)...)
} }
if err := ws.GenerateOrder(purchased, bookings, request); err != nil {
return false, wf, execs, purchased, bookings, err
}
errCh := make(chan error, len(bookings)) errCh := make(chan error, len(bookings))
var m sync.Mutex var m sync.Mutex
@ -107,6 +101,10 @@ func (ws *WorkflowSchedule) GetBuyAndBook(wfID string, request *tools.APIRequest
} }
} }
if err := ws.GenerateOrder(purchased, bookings, request); err != nil {
return false, wf, execs, purchased, bookings, err
}
return true, wf, execs, purchased, bookings, nil return true, wf, execs, purchased, bookings, nil
} }
@ -122,7 +120,7 @@ func (ws *WorkflowSchedule) GenerateOrder(purchases []*purchase_resource.Purchas
Status: enum.PENDING, Status: enum.PENDING,
} }
if res, _, err := order.NewAccessor(request).StoreOne(newOrder); err == nil { if res, _, err := order.NewAccessor(request).StoreOne(newOrder); err == nil {
if _, err := bill.DraftBill(res.(*order.Order), request); err != nil { if _, err := bill.DraftFirstBill(res.(*order.Order), request); err != nil {
return err return err
} }
return nil return nil