Skip to content

Commit

Permalink
Add compatibility mode for creating odoo16 sales orders using odoo8 IDs
Browse files Browse the repository at this point in the history
  • Loading branch information
HappyTetrahedron committed Dec 18, 2023
1 parent 1f9af43 commit 1ff9a3e
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 9 deletions.
4 changes: 4 additions & 0 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func ControllerCommand() *cobra.Command {
billingEntityEmailSubject := cmd.Flags().String("billingentity-email-subject", "An APPUiO Billing Entity has been updated", "Subject for billing entity modification update mails")
billingEntityCronInterval := cmd.Flags().String("billingentity-email-cron-interval", "@every 1h", "Cron interval for how frequently billing entity update e-mails are sent")

saleOrderCompatMode := cmd.Flags().Bool("sale-order-compatibility-mode", false, "Whether to enable compatibility mode for Sales Orders. If enabled, odoo8 billing entity IDs are used to create sales orders in odoo16.")
saleOrderStorage := cmd.Flags().String("sale-order-storage", "none", "Type of sale order storage to use. Valid values are `none` and `odoo16`")
saleOrderClientReference := cmd.Flags().String("sale-order-client-reference", "APPUiO Cloud", "Default client reference to add to newly created sales orders.")
saleOrderInternalNote := cmd.Flags().String("sale-order-internal-note", "auto-generated by APPUiO Cloud Control API", "Default internal note to add to newly created sales orders.")
Expand Down Expand Up @@ -195,6 +196,7 @@ func ControllerCommand() *cobra.Command {
*saleOrderStorage,
*saleOrderClientReference,
*saleOrderInternalNote,
*saleOrderCompatMode,
oc,
ctrl.Options{
Scheme: scheme,
Expand Down Expand Up @@ -245,6 +247,7 @@ func setupManager(
saleOrderStorage string,
saleOrderClientReference string,
saleOrderInternalNote string,
saleOrderCompatMode bool,
odooCredentials saleorder.Odoo16Credentials,
opt ctrl.Options,
) (ctrl.Manager, error) {
Expand Down Expand Up @@ -346,6 +349,7 @@ func setupManager(
storage, err := saleorder.NewOdoo16Storage(&odooCredentials, &saleorder.Odoo16Options{
SaleOrderClientReferencePrefix: saleOrderClientReference,
SaleOrderInternalNote: saleOrderInternalNote,
Odoo8CompatibilityMode: saleOrderCompatMode,
})
if err != nil {
return nil, err
Expand Down
15 changes: 15 additions & 0 deletions controllers/saleorder/mock_saleorder/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 31 additions & 8 deletions controllers/saleorder/saleorder_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Odoo16Credentials = odooclient.ClientConfig
type Odoo16Options struct {
SaleOrderClientReferencePrefix string
SaleOrderInternalNote string
Odoo8CompatibilityMode bool
}

const defaultSaleOrderState = "sale"
Expand All @@ -26,6 +27,7 @@ type SaleOrderStorage interface {
type Odoo16Client interface {
Read(string, []int64, *odooclient.Options, interface{}) error
CreateSaleOrder(*odooclient.SaleOrder) (int64, error)
FindResPartners(*odooclient.Criteria, *odooclient.Options) (*odooclient.ResPartners, error)
}

type Odoo16SaleOrderStorage struct {
Expand Down Expand Up @@ -54,21 +56,42 @@ func (s *Odoo16SaleOrderStorage) CreateSaleOrder(org organizationv1.Organization
return "", err
}

var beRecord odooclient.ResPartner

fetchPartnerFieldOpts := odooclient.NewOptions().FetchFields(
"id",
"parent_id",
)

beRecords := []odooclient.ResPartner{}
err = s.client.Read(odooclient.ResPartnerModel, []int64{int64(beID)}, fetchPartnerFieldOpts, &beRecords)
if err != nil {
return "", fmt.Errorf("fetching accounting contact by ID: %w", err)
}
if s.options.Odoo8CompatibilityMode {
odoo8ID := fmt.Sprintf("__export__.res_partner_%d", beID)

idMatchCriteria := odooclient.NewCriteria().Add("x_odoo_8_ID", "=", odoo8ID)

r, err := s.client.FindResPartners(idMatchCriteria, fetchPartnerFieldOpts)
if err != nil {
return "", fmt.Errorf("fetching accounting contact by ID: %w", err)
}

if len(*r) <= 0 {
return "", fmt.Errorf("no results when fetching accounting contact by ID")
}
resPartners := *r

beRecord = resPartners[0]
} else {
beRecords := []odooclient.ResPartner{}
err = s.client.Read(odooclient.ResPartnerModel, []int64{int64(beID)}, fetchPartnerFieldOpts, &beRecords)
if err != nil {
return "", fmt.Errorf("fetching accounting contact by ID: %w", err)
}

if len(beRecords) <= 0 {
return "", fmt.Errorf("no results when fetching accounting contact by ID")
}

if len(beRecords) <= 0 {
return "", fmt.Errorf("no results when fetching accounting contact by ID")
beRecord = beRecords[0]
}
beRecord := beRecords[0]

if beRecord.ParentId == nil {
return "", fmt.Errorf("accounting contact %d has no parent", beRecord.Id.Get())
Expand Down
85 changes: 84 additions & 1 deletion controllers/saleorder/saleorder_storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,37 @@ import (
odooclient "github.com/appuio/go-odoo"
)

func TestCreateCompat(t *testing.T) {
ctrl, mock, subject := createStorageCompat(t)
defer ctrl.Finish()

tn := time.Now()
st, _ := time.Parse(time.RFC3339, "2023-04-18T14:07:55Z")
statusTime := st.Local()

gomock.InOrder(
mock.EXPECT().FindResPartners(gomock.Any(), gomock.Any()).Return(&odooclient.ResPartners{{
Id: odooclient.NewInt(456),
CreateDate: odooclient.NewTime(tn),
ParentId: odooclient.NewMany2One(123, ""),
Email: odooclient.NewString("[email protected], [email protected]"),
VshnControlApiMetaStatus: odooclient.NewString("{\"conditions\":[{\"type\":\"ConditionFoo\",\"status\":\"False\",\"lastTransitionTime\":\"" + statusTime.Format(time.RFC3339) + "\",\"reason\":\"Whatever\",\"message\":\"Hello World\"}]}"),
}}, nil),
mock.EXPECT().CreateSaleOrder(gomock.Any()).Return(int64(149), nil),
)

soid, err := subject.CreateSaleOrder(organizationv1.Organization{
ObjectMeta: metav1.ObjectMeta{
Name: "myorg",
},
Spec: organizationv1.OrganizationSpec{
BillingEntityRef: "be-123",
},
})
require.NoError(t, err)
assert.Equal(t, "149", soid)
}

func TestCreate(t *testing.T) {
ctrl, mock, subject := createStorage(t)
defer ctrl.Finish()
Expand Down Expand Up @@ -48,7 +79,7 @@ func TestCreate(t *testing.T) {
}

func TestGet(t *testing.T) {
ctrl, mock, subject := createStorage(t)
ctrl, mock, subject := createStorageCompat(t)
defer ctrl.Finish()

gomock.InOrder(
Expand All @@ -73,6 +104,43 @@ func TestGet(t *testing.T) {
assert.Equal(t, "SO149", soid)
}

func TestCreateAttributesCompat(t *testing.T) {
ctrl, mock, subject := createStorageCompat(t)
defer ctrl.Finish()

tn := time.Now()
st, _ := time.Parse(time.RFC3339, "2023-04-18T14:07:55Z")
statusTime := st.Local()

gomock.InOrder(
mock.EXPECT().FindResPartners(gomock.Any(), gomock.Any()).Return(&odooclient.ResPartners{{
Id: odooclient.NewInt(456),
CreateDate: odooclient.NewTime(tn),
ParentId: odooclient.NewMany2One(123, ""),
Email: odooclient.NewString("[email protected], [email protected]"),
VshnControlApiMetaStatus: odooclient.NewString("{\"conditions\":[{\"type\":\"ConditionFoo\",\"status\":\"False\",\"lastTransitionTime\":\"" + statusTime.Format(time.RFC3339) + "\",\"reason\":\"Whatever\",\"message\":\"Hello World\"}]}"),
}}, nil),
mock.EXPECT().CreateSaleOrder(SaleOrderMatcher{
PartnerId: int64(123),
PartnerInvoiceId: int64(456),
State: "sale",
ClientOrderRef: "client-ref (myorg)",
InternalNote: "internal-note",
}).Return(int64(149), nil),
)

soid, err := subject.CreateSaleOrder(organizationv1.Organization{
ObjectMeta: metav1.ObjectMeta{
Name: "myorg",
},
Spec: organizationv1.OrganizationSpec{
BillingEntityRef: "be-123",
},
})
require.NoError(t, err)
assert.Equal(t, "149", soid)
}

func TestCreateAttributes(t *testing.T) {
ctrl, mock, subject := createStorage(t)
defer ctrl.Finish()
Expand Down Expand Up @@ -126,6 +194,20 @@ func (s SaleOrderMatcher) String() string {
return fmt.Sprintf("{PartnerId:%d PartnerInvoiceId:%d State:%s ClientOrderRef:%s InternalNote:%s}", s.PartnerId, s.PartnerInvoiceId, s.State, s.ClientOrderRef, s.InternalNote)
}

func createStorageCompat(t *testing.T) (*gomock.Controller, *mock_saleorder.MockOdoo16Client, saleorder.SaleOrderStorage) {
ctrl := gomock.NewController(t)
mock := mock_saleorder.NewMockOdoo16Client(ctrl)

return ctrl, mock, saleorder.NewOdoo16StorageFromClient(
mock,
&saleorder.Odoo16Options{
SaleOrderClientReferencePrefix: "client-ref",
SaleOrderInternalNote: "internal-note",
Odoo8CompatibilityMode: true,
},
)
}

func createStorage(t *testing.T) (*gomock.Controller, *mock_saleorder.MockOdoo16Client, saleorder.SaleOrderStorage) {
ctrl := gomock.NewController(t)
mock := mock_saleorder.NewMockOdoo16Client(ctrl)
Expand All @@ -135,6 +217,7 @@ func createStorage(t *testing.T) (*gomock.Controller, *mock_saleorder.MockOdoo16
&saleorder.Odoo16Options{
SaleOrderClientReferencePrefix: "client-ref",
SaleOrderInternalNote: "internal-note",
Odoo8CompatibilityMode: false,
},
)
}

0 comments on commit 1ff9a3e

Please sign in to comment.