chain and tx controllers merge

This commit is contained in:
r8zavetr8v 2024-05-25 23:38:47 +03:00
parent 223ad9c31e
commit 2a249e3979
11 changed files with 864 additions and 33 deletions

View File

@ -226,6 +226,60 @@ Response:
} }
``` ```
## GET **/{organization_id}/participants**
### Request body:
```json
{
"limit":1
}
```
### Example
Request:
```bash
curl --request GET \
--url http://localhost:8081/organizations/018faff4-481f-73ec-a4b8-27ef07b4029b/participants \
--header 'Authorization: Bearer TOKEN' \
--header 'content-type: application/json' \
--data '{
"limit":1
}'
```
Response:
```json
{
"_type": "participants",
"_links": {
"self": {
"href": "/organizations/018faff4-481f-73ec-a4b8-27ef07b4029b/participants"
}
},
"participants": [
{
"_type": "participant",
"_links": {
"self": {
"href": "/organizations/018faff4-481f-73ec-a4b8-27ef07b4029b/participants018faff4-25fb-7973-860a-59eb69b766a4"
}
},
"id": "018faff4-25fb-7973-860a-59eb69b766a4",
"name": "Bladee The Grand Drainer",
"credentials": {
"email": "bladeee@gmail.com",
"phone": "+79999999999",
"telegram": "@thebladee"
},
"created_at": 1716654773151,
"updated_at": 1716654773151,
"is_user": true,
"is_admin": true,
"is_owner": true,
"is_active": true
}
]
}
```
## GET **/{organization_id}/transactions** ## GET **/{organization_id}/transactions**
### Request body: ### Request body:

View File

@ -11,6 +11,7 @@ import (
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters" "github.com/emochka2007/block-accounting/internal/interface/rest/presenters"
"github.com/emochka2007/block-accounting/internal/pkg/config" "github.com/emochka2007/block-accounting/internal/pkg/config"
"github.com/emochka2007/block-accounting/internal/pkg/logger" "github.com/emochka2007/block-accounting/internal/pkg/logger"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/chain"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations" "github.com/emochka2007/block-accounting/internal/usecase/interactors/organizations"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions" "github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions"
@ -90,11 +91,13 @@ func provideOrganizationsController(
func provideTxController( func provideTxController(
log *slog.Logger, log *slog.Logger,
txInteractor transactions.TransactionsInteractor, txInteractor transactions.TransactionsInteractor,
chainInteractor chain.ChainInteractor,
) controllers.TransactionsController { ) controllers.TransactionsController {
return controllers.NewTransactionsController( return controllers.NewTransactionsController(
log.WithGroup("transactions-controller"), log.WithGroup("transactions-controller"),
txInteractor, txInteractor,
presenters.NewTransactionsPresenter(), presenters.NewTransactionsPresenter(),
chainInteractor,
) )
} }

View File

@ -35,7 +35,7 @@ func ProvideService(c config.Config) (service.Service, func(), error) {
organizationsPresenter := provideOrganizationsPresenter() organizationsPresenter := provideOrganizationsPresenter()
organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter) organizationsController := provideOrganizationsController(logger, organizationsInteractor, organizationsPresenter)
transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor) transactionsInteractor := provideTxInteractor(logger, transactionsRepository, organizationsInteractor)
transactionsController := provideTxController(logger, transactionsInteractor) transactionsController := provideTxController(logger, transactionsInteractor, chainInteractor)
participantsController := provideParticipantsController(logger, organizationsInteractor, usersInteractor) participantsController := provideParticipantsController(logger, organizationsInteractor, usersInteractor)
rootController := provideControllers(logger, authController, organizationsController, transactionsController, participantsController) rootController := provideControllers(logger, authController, organizationsController, transactionsController, participantsController)
server := provideRestServer(logger, rootController, c, jwtInteractor) server := provideRestServer(logger, rootController, c, jwtInteractor)

View File

@ -14,31 +14,45 @@ import (
"github.com/emochka2007/block-accounting/internal/interface/rest/presenters" "github.com/emochka2007/block-accounting/internal/interface/rest/presenters"
"github.com/emochka2007/block-accounting/internal/pkg/ctxmeta" "github.com/emochka2007/block-accounting/internal/pkg/ctxmeta"
"github.com/emochka2007/block-accounting/internal/pkg/models" "github.com/emochka2007/block-accounting/internal/pkg/models"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/chain"
"github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions" "github.com/emochka2007/block-accounting/internal/usecase/interactors/transactions"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
// TODO по хорошему это уебищу надо разносить, но ни времени ни сил пока нет
// в рамках рефакторинка не забыть
// TransactionsController | ChainController
type TransactionsController interface { type TransactionsController interface {
New(w http.ResponseWriter, r *http.Request) ([]byte, error) New(w http.ResponseWriter, r *http.Request) ([]byte, error)
List(w http.ResponseWriter, r *http.Request) ([]byte, error) List(w http.ResponseWriter, r *http.Request) ([]byte, error)
UpdateStatus(w http.ResponseWriter, r *http.Request) ([]byte, error) UpdateStatus(w http.ResponseWriter, r *http.Request) ([]byte, error)
NewPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error)
ConfirmPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error)
ListPayrolls(w http.ResponseWriter, r *http.Request) ([]byte, error)
NewMultisig(w http.ResponseWriter, r *http.Request) ([]byte, error)
ListMultisigs(w http.ResponseWriter, r *http.Request) ([]byte, error)
} }
type transactionsController struct { type transactionsController struct {
log *slog.Logger log *slog.Logger
txInteractor transactions.TransactionsInteractor txInteractor transactions.TransactionsInteractor
txPresenter presenters.TransactionsPresenter txPresenter presenters.TransactionsPresenter
chainInteractor chain.ChainInteractor
} }
func NewTransactionsController( func NewTransactionsController(
log *slog.Logger, log *slog.Logger,
txInteractor transactions.TransactionsInteractor, txInteractor transactions.TransactionsInteractor,
txPresenter presenters.TransactionsPresenter, txPresenter presenters.TransactionsPresenter,
chainInteractor chain.ChainInteractor,
) TransactionsController { ) TransactionsController {
return &transactionsController{ return &transactionsController{
log: log, log: log,
txInteractor: txInteractor, txInteractor: txInteractor,
txPresenter: txPresenter, txPresenter: txPresenter,
chainInteractor: chainInteractor,
} }
} }
@ -187,7 +201,59 @@ func (c *transactionsController) UpdateStatus(w http.ResponseWriter, r *http.Req
return c.txPresenter.ResponseNewTransaction(ctx, tx) return c.txPresenter.ResponseNewTransaction(ctx, tx)
} }
// todo creates a new payout func (c *transactionsController) NewMultisig(w http.ResponseWriter, r *http.Request) ([]byte, error) {
func (c *transactionsController) NewPayout(w http.ResponseWriter, r *http.Request) ([]byte, error) { req, err := presenters.CreateRequest[domain.NewMultisigRequest](r)
if err != nil {
return nil, fmt.Errorf("error build new transaction request. %w", err)
}
organizationID, err := ctxmeta.OrganizationId(r.Context())
if err != nil {
return nil, fmt.Errorf("error fetch organization ID from context. %w", err)
}
c.log.Debug(
"new multisig request",
slog.String("org id", organizationID.String()),
slog.Any("req", req),
)
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
ownersPKs := make([]string, len(req.Owners))
for i, pk := range req.Owners {
ownersPKs[i] = pk.PublicKey
}
if req.Confirmations <= 0 {
req.Confirmations = 1
}
if err := c.chainInteractor.NewMultisig(ctx, chain.NewMultisigParams{
OwnersPKs: ownersPKs,
Confirmations: req.Confirmations,
}); err != nil {
return nil, fmt.Errorf("error deploy multisig. %w", err)
}
return presenters.ResponseOK()
}
func (s *transactionsController) ListMultisigs(w http.ResponseWriter, r *http.Request) ([]byte, error) {
return nil, nil
}
// todo creates a new payout
func (c *transactionsController) NewPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error) {
return nil, nil
}
func (c *transactionsController) ConfirmPayroll(w http.ResponseWriter, r *http.Request) ([]byte, error) {
return nil, nil
}
func (c *transactionsController) ListPayrolls(w http.ResponseWriter, r *http.Request) ([]byte, error) {
return nil, nil return nil, nil
} }

View File

@ -112,6 +112,7 @@ type NewMultisigRequest struct {
Owners []struct { Owners []struct {
PublicKey string `json:"public_key"` PublicKey string `json:"public_key"`
} `json:"owners"` } `json:"owners"`
Confirmations int `json:confirmations`
} }
func BuildRequest[T any](data []byte) (*T, error) { func BuildRequest[T any](data []byte) (*T, error) {

View File

@ -56,6 +56,7 @@ func (p *participantsPresenter) responseParticipant(
domainParticipant.IsUser = true domainParticipant.IsUser = true
domainParticipant.IsAdmin = user.IsAdmin() domainParticipant.IsAdmin = user.IsAdmin()
domainParticipant.IsOwner = user.IsOwner() domainParticipant.IsOwner = user.IsOwner()
domainParticipant.IsActive = user.Activated
} }

View File

@ -23,3 +23,11 @@ func CreateRequest[T any](r *http.Request) (*T, error) {
return &request, nil return &request, nil
} }
type ok struct {
Ok bool
}
func ResponseOK() ([]byte, error) {
return json.Marshal(&ok{Ok: true})
}

View File

@ -114,18 +114,21 @@ func (s *Server) buildRouter() {
}) })
r.Route("/payrolls", func(r chi.Router) { r.Route("/payrolls", func(r chi.Router) {
r.Get("/", nil) // list payrolls r.Get("/", s.handle(s.controllers.Transactions.ListPayrolls, "list_payrolls"))
r.Post("/", nil) // deploy contract r.Post("/", s.handle(s.controllers.Transactions.NewPayroll, "new_payroll"))
r.Put("/", nil) // todo
}) })
r.Route("/multisig", func(r chi.Router) { r.Route("/multisig", func(r chi.Router) {
r.Post("/", nil) // new multisig (deploy) r.Post("/", s.handle(s.controllers.Transactions.NewMultisig, "new_multisig"))
r.Get("/", nil) // list r.Get("/", s.handle(s.controllers.Transactions.ListMultisigs, "list_multisig"))
r.Put("/", nil) // todo
}) })
r.Route("/license", func(r chi.Router) { r.Route("/license", func(r chi.Router) {
r.Get("/", nil) // list license r.Get("/", nil) // list license
r.Post("/", nil) // deploy contract r.Post("/", nil) // deploy contract
r.Put("/", nil) // todo
}) })
// join via invite link // join via invite link
@ -133,20 +136,20 @@ func (s *Server) buildRouter() {
r.Route("/participants", func(r chi.Router) { r.Route("/participants", func(r chi.Router) {
r.Get("/", s.handle(s.controllers.Participants.List, "participants_list")) r.Get("/", s.handle(s.controllers.Participants.List, "participants_list"))
r.Post("/", nil) r.Post("/", s.handle(s.controllers.Participants.New, "new_participant"))
// generate new invite link // generate new invite link
r.Post("/invite", s.handle(s.controllers.Auth.Invite, "invite")) r.Post("/invite", s.handle(s.controllers.Auth.Invite, "invite"))
r.Route("/{participant_id}", func(r chi.Router) { r.Route("/{participant_id}", func(r chi.Router) {
r.Put("/", nil) // update user / employee r.Get("/", nil)
r.Delete("/", nil) // remove user / employee // r.Put("/", nil) // update user / employee
// r.Delete("/", nil) // remove user / employee
r.Route("/payroll", func(r chi.Router) { // r.Route("/payroll", func(r chi.Router) {
r.Post("/", nil) // set salary // r.Put("/", nil) // edit
r.Put("/", nil) // edit // r.Get("/", nil)
r.Get("/", nil) // })
})
}) })
}) })
}) })

View File

@ -358,12 +358,93 @@ func (r *repositorySQL) Participants(
participants := make([]models.OrganizationParticipant, 0, len(params.Ids)) participants := make([]models.OrganizationParticipant, 0, len(params.Ids))
if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) { if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) (err error) {
orgUsersModels, err := r.fetchOrganizationUsers(ctx, params) orgUsersModels := make([]fetchOrganizationUsersModel, 0, len(params.Ids))
if err != nil {
return fmt.Errorf("error fetch organization users raw models. %w", err) ouQuery := sq.Select(
"ou.organization_id",
"ou.user_id",
"ou.employee_id",
"ou.position",
"ou.added_at",
"ou.updated_at",
"ou.deleted_at",
"ou.is_admin",
"ou.is_owner",
).Where(sq.Eq{
"ou.organization_id": params.OrganizationId,
}).From("organizations_users as ou").
PlaceholderFormat(sq.Dollar)
if len(params.Ids) > 0 {
ouQuery = ouQuery.Where(sq.Eq{
"ou.user_id": params.Ids,
})
} }
eg, egCtx := errgroup.WithContext(ctx) rows, err := ouQuery.RunWith(r.Conn(ctx)).QueryContext(ctx)
if err != nil {
return fmt.Errorf("error fetch organization participants. %w", err)
}
defer func() {
if closeErr := rows.Close(); closeErr != nil {
err = errors.Join(fmt.Errorf("error close rows. %w", closeErr), err)
}
}()
for rows.Next() {
var (
organizationID uuid.UUID
userID uuid.UUID
employeeID uuid.UUID
position sql.NullString
addedAt time.Time
updatedAt time.Time
deletedAt sql.NullTime
isAdmin bool
isOwner bool
)
if err = rows.Scan(
&organizationID,
&userID,
&employeeID,
&position,
&addedAt,
&updatedAt,
&deletedAt,
&isAdmin,
&isOwner,
); err != nil {
return fmt.Errorf("error scan row. %w", err)
}
if params.EmployeesOnly && employeeID == uuid.Nil {
continue
}
if params.UsersOnly && userID == uuid.Nil {
continue
}
if params.ActiveOnly && deletedAt.Valid {
continue
}
orgUsersModels = append(orgUsersModels, fetchOrganizationUsersModel{
organizationID: organizationID,
userID: userID,
employeeID: employeeID,
position: position.String,
addedAt: addedAt,
updatedAt: updatedAt,
deletedAt: deletedAt.Time,
isAdmin: isAdmin,
isOwner: isOwner,
})
}
eg, _ := errgroup.WithContext(ctx)
var employees []*models.Employee = make([]*models.Employee, 0, len(orgUsersModels)) var employees []*models.Employee = make([]*models.Employee, 0, len(orgUsersModels))
if !params.UsersOnly { if !params.UsersOnly {
@ -376,12 +457,66 @@ func (r *repositorySQL) Participants(
} }
} }
employees, err = r.fetchEmployees(egCtx, fetchEmployeesParams{ query := sq.Select(
IDs: ids, "e.id",
OrganizationId: params.OrganizationId, "e.user_id",
}) "e.organization_id",
"e.wallet_address",
"e.created_at",
"e.updated_at",
).Where(sq.Eq{
"e.organization_id": params.OrganizationId,
}).From("employees as e").
PlaceholderFormat(sq.Dollar)
if len(ids) > 0 {
query = query.Where(sq.Eq{
"e.id": ids,
})
}
fmt.Println(query.ToSql())
rows, err := query.RunWith(r.Conn(ctx)).QueryContext(ctx)
if err != nil { if err != nil {
return fmt.Errorf("error fetch employees. %w", err) return fmt.Errorf("error fetch employees from database. %w", err)
}
defer func() {
if closeErr := rows.Close(); closeErr != nil {
err = errors.Join(fmt.Errorf("error close rows. %w", closeErr), err)
}
}()
for rows.Next() {
var (
id uuid.UUID
userID uuid.UUID
orgID uuid.UUID
walletAddr []byte
createdAt time.Time
updatedAt time.Time
)
if err = rows.Scan(
&id,
&userID,
&orgID,
&walletAddr,
&createdAt,
&updatedAt,
); err != nil {
return fmt.Errorf("error scan row. %w", err)
}
employees = append(employees, &models.Employee{
ID: id,
UserID: userID,
OrganizationId: orgID,
WalletAddress: walletAddr,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
} }
return nil return nil
@ -399,7 +534,10 @@ func (r *repositorySQL) Participants(
} }
} }
usrs, err = r.usersRepository.Get(egCtx, users.GetParams{ usersCtx, cancel := context.WithTimeout(context.TODO(), 3*time.Second)
defer cancel()
usrs, err = r.usersRepository.Get(usersCtx, users.GetParams{
Ids: ids, Ids: ids,
}) })
if err != nil { if err != nil {
@ -437,7 +575,11 @@ func (r *repositorySQL) Participants(
User: *u, User: *u,
OrgPosition: ou.position, OrgPosition: ou.position,
Admin: ou.isAdmin, Admin: ou.isAdmin,
Owner: ou.isOwner,
Employee: employee, Employee: employee,
CreatedAt: ou.addedAt,
UpdatedAt: ou.updatedAt,
DeletedAt: ou.deletedAt,
}) })
break break
@ -497,8 +639,6 @@ func (r *repositorySQL) fetchOrganizationUsers(
}) })
} }
fmt.Println(ouQuery.ToSql())
rows, err := ouQuery.RunWith(r.Conn(ctx)).QueryContext(ctx) rows, err := ouQuery.RunWith(r.Conn(ctx)).QueryContext(ctx)
if err != nil { if err != nil {
return fmt.Errorf("error fetch organization participants. %w", err) return fmt.Errorf("error fetch organization participants. %w", err)

View File

@ -8,6 +8,7 @@ create table if not exists users (
mnemonic varchar(500) not null, mnemonic varchar(500) not null,
seed bytea not null unique, seed bytea not null unique,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp,
activated_at timestamp default null activated_at timestamp default null
); );

554
backend/test.txt Normal file
View File

@ -0,0 +1,554 @@
<Valuta name="Foreign Currency Market Lib">
<Item ID="R01010">
<Name>Австралийский доллар</Name>
<EngName>Australian Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01010 </ParentCode>
<ISO_Num_Code>36</ISO_Num_Code>
<ISO_Char_Code>AUD</ISO_Char_Code>
</Item>
<Item ID="R01015">
<Name>Австрийский шиллинг</Name>
<EngName>Austrian Shilling</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01015 </ParentCode>
<ISO_Num_Code>40</ISO_Num_Code>
<ISO_Char_Code>ATS</ISO_Char_Code>
</Item>
<Item ID="R01020A">
<Name>Азербайджанский манат</Name>
<EngName>Azerbaijan Manat</EngName>
<Nominal>1</Nominal>
<ParentCode>R01020 </ParentCode>
<ISO_Num_Code>944</ISO_Num_Code>
<ISO_Char_Code>AZN</ISO_Char_Code>
</Item>
<Item ID="R01035">
<Name>Фунт стерлингов Соединенного королевства</Name>
<EngName>British Pound Sterling</EngName>
<Nominal>1</Nominal>
<ParentCode>R01035 </ParentCode>
<ISO_Num_Code>826</ISO_Num_Code>
<ISO_Char_Code>GBP</ISO_Char_Code>
</Item>
<Item ID="R01040F">
<Name>Ангольская новая кванза</Name>
<EngName>Angolan new Kwanza</EngName>
<Nominal>100000</Nominal>
<ParentCode>R01040 </ParentCode>
<ISO_Num_Code>24</ISO_Num_Code>
<ISO_Char_Code>AON</ISO_Char_Code>
</Item>
<Item ID="R01060">
<Name>Армянский драм</Name>
<EngName>Armenia Dram</EngName>
<Nominal>100</Nominal>
<ParentCode>R01060 </ParentCode>
<ISO_Num_Code>51</ISO_Num_Code>
<ISO_Char_Code>AMD</ISO_Char_Code>
</Item>
<Item ID="R01090B">
<Name>Белорусский рубль</Name>
<EngName>Belarussian Ruble</EngName>
<Nominal>1</Nominal>
<ParentCode>R01090 </ParentCode>
<ISO_Num_Code>933</ISO_Num_Code>
<ISO_Char_Code>BYN</ISO_Char_Code>
</Item>
<Item ID="R01095">
<Name>Бельгийский франк</Name>
<EngName>Belgium Franc</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01095 </ParentCode>
<ISO_Num_Code>56</ISO_Num_Code>
<ISO_Char_Code>BEF</ISO_Char_Code>
</Item>
<Item ID="R01100">
<Name>Болгарский лев</Name>
<EngName>Bulgarian lev</EngName>
<Nominal>1</Nominal>
<ParentCode>R01100 </ParentCode>
<ISO_Num_Code>975</ISO_Num_Code>
<ISO_Char_Code>BGN</ISO_Char_Code>
</Item>
<Item ID="R01115">
<Name>Бразильский реал</Name>
<EngName>Brazil Real</EngName>
<Nominal>1</Nominal>
<ParentCode>R01115 </ParentCode>
<ISO_Num_Code>986</ISO_Num_Code>
<ISO_Char_Code>BRL</ISO_Char_Code>
</Item>
<Item ID="R01135">
<Name>Венгерский форинт</Name>
<EngName>Hungarian Forint</EngName>
<Nominal>100</Nominal>
<ParentCode>R01135 </ParentCode>
<ISO_Num_Code>348</ISO_Num_Code>
<ISO_Char_Code>HUF</ISO_Char_Code>
</Item>
<Item ID="R01150">
<Name>Вьетнамский донг</Name>
<EngName>Vietnam Dong</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01150 </ParentCode>
<ISO_Num_Code>704</ISO_Num_Code>
<ISO_Char_Code>VND</ISO_Char_Code>
</Item>
<Item ID="R01200">
<Name>Гонконгский доллар</Name>
<EngName>Hong Kong Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01200 </ParentCode>
<ISO_Num_Code>344</ISO_Num_Code>
<ISO_Char_Code>HKD</ISO_Char_Code>
</Item>
<Item ID="R01205">
<Name>Греческая драхма</Name>
<EngName>Greek Drachma</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01205 </ParentCode>
<ISO_Num_Code>300</ISO_Num_Code>
<ISO_Char_Code>GRD</ISO_Char_Code>
</Item>
<Item ID="R01210">
<Name>Грузинский лари</Name>
<EngName>Georgia Lari</EngName>
<Nominal>1</Nominal>
<ParentCode>R01210 </ParentCode>
<ISO_Num_Code>981</ISO_Num_Code>
<ISO_Char_Code>GEL</ISO_Char_Code>
</Item>
<Item ID="R01215">
<Name>Датская крона</Name>
<EngName>Danish Krone</EngName>
<Nominal>1</Nominal>
<ParentCode>R01215 </ParentCode>
<ISO_Num_Code>208</ISO_Num_Code>
<ISO_Char_Code>DKK</ISO_Char_Code>
</Item>
<Item ID="R01230">
<Name>Дирхам ОАЭ</Name>
<EngName>UAE Dirham</EngName>
<Nominal>1</Nominal>
<ParentCode>R01230 </ParentCode>
<ISO_Num_Code>784</ISO_Num_Code>
<ISO_Char_Code>AED</ISO_Char_Code>
</Item>
<Item ID="R01235">
<Name>Доллар США</Name>
<EngName>US Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01235 </ParentCode>
<ISO_Num_Code>840</ISO_Num_Code>
<ISO_Char_Code>USD</ISO_Char_Code>
</Item>
<Item ID="R01239">
<Name>Евро</Name>
<EngName>Euro</EngName>
<Nominal>1</Nominal>
<ParentCode>R01239 </ParentCode>
<ISO_Num_Code>978</ISO_Num_Code>
<ISO_Char_Code>EUR</ISO_Char_Code>
</Item>
<Item ID="R01240">
<Name>Египетский фунт</Name>
<EngName>Egyptian Pound</EngName>
<Nominal>10</Nominal>
<ParentCode>R01240 </ParentCode>
<ISO_Num_Code>818</ISO_Num_Code>
<ISO_Char_Code>EGP</ISO_Char_Code>
</Item>
<Item ID="R01270">
<Name>Индийская рупия</Name>
<EngName>Indian Rupee</EngName>
<Nominal>10</Nominal>
<ParentCode>R01270 </ParentCode>
<ISO_Num_Code>356</ISO_Num_Code>
<ISO_Char_Code>INR</ISO_Char_Code>
</Item>
<Item ID="R01280">
<Name>Индонезийская рупия</Name>
<EngName>Indonesian Rupiah</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01280 </ParentCode>
<ISO_Num_Code>360</ISO_Num_Code>
<ISO_Char_Code>IDR</ISO_Char_Code>
</Item>
<Item ID="R01305">
<Name>Ирландский фунт</Name>
<EngName>Irish Pound</EngName>
<Nominal>100</Nominal>
<ParentCode>R01305 </ParentCode>
<ISO_Num_Code>372</ISO_Num_Code>
<ISO_Char_Code>IEP</ISO_Char_Code>
</Item>
<Item ID="R01310">
<Name>Исландская крона</Name>
<EngName>Iceland Krona</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01310 </ParentCode>
<ISO_Num_Code>352</ISO_Num_Code>
<ISO_Char_Code>ISK</ISO_Char_Code>
</Item>
<Item ID="R01315">
<Name>Испанская песета</Name>
<EngName>Spanish Peseta</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01315 </ParentCode>
<ISO_Num_Code>724</ISO_Num_Code>
<ISO_Char_Code>ESP</ISO_Char_Code>
</Item>
<Item ID="R01325">
<Name>Итальянская лира</Name>
<EngName>Italian Lira</EngName>
<Nominal>100000</Nominal>
<ParentCode>R01325 </ParentCode>
<ISO_Num_Code>380</ISO_Num_Code>
<ISO_Char_Code>ITL</ISO_Char_Code>
</Item>
<Item ID="R01335">
<Name>Казахстанский тенге</Name>
<EngName>Kazakhstan Tenge</EngName>
<Nominal>100</Nominal>
<ParentCode>R01335 </ParentCode>
<ISO_Num_Code>398</ISO_Num_Code>
<ISO_Char_Code>KZT</ISO_Char_Code>
</Item>
<Item ID="R01350">
<Name>Канадский доллар</Name>
<EngName>Canadian Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01350 </ParentCode>
<ISO_Num_Code>124</ISO_Num_Code>
<ISO_Char_Code>CAD</ISO_Char_Code>
</Item>
<Item ID="R01355">
<Name>Катарский риал</Name>
<EngName>Qatari Riyal</EngName>
<Nominal>1</Nominal>
<ParentCode>R01355 </ParentCode>
<ISO_Num_Code>634</ISO_Num_Code>
<ISO_Char_Code>QAR</ISO_Char_Code>
</Item>
<Item ID="R01370">
<Name>Киргизский сом</Name>
<EngName>Kyrgyzstan Som</EngName>
<Nominal>10</Nominal>
<ParentCode>R01370 </ParentCode>
<ISO_Num_Code>417</ISO_Num_Code>
<ISO_Char_Code>KGS</ISO_Char_Code>
</Item>
<Item ID="R01375">
<Name>Китайский юань</Name>
<EngName>China Yuan</EngName>
<Nominal>1</Nominal>
<ParentCode>R01375 </ParentCode>
<ISO_Num_Code>156</ISO_Num_Code>
<ISO_Char_Code>CNY</ISO_Char_Code>
</Item>
<Item ID="R01390">
<Name>Кувейтский динар</Name>
<EngName>Kuwaiti Dinar</EngName>
<Nominal>10</Nominal>
<ParentCode>R01390 </ParentCode>
<ISO_Num_Code>414</ISO_Num_Code>
<ISO_Char_Code>KWD</ISO_Char_Code>
</Item>
<Item ID="R01405">
<Name>Латвийский лат</Name>
<EngName>Latvian Lat</EngName>
<Nominal>1</Nominal>
<ParentCode>R01405 </ParentCode>
<ISO_Num_Code>428</ISO_Num_Code>
<ISO_Char_Code>LVL</ISO_Char_Code>
</Item>
<Item ID="R01420">
<Name>Ливанский фунт</Name>
<EngName>Lebanese Pound</EngName>
<Nominal>100000</Nominal>
<ParentCode>R01420 </ParentCode>
<ISO_Num_Code>422</ISO_Num_Code>
<ISO_Char_Code>LBP</ISO_Char_Code>
</Item>
<Item ID="R01435">
<Name>Литовский лит</Name>
<EngName>Lithuanian Lita</EngName>
<Nominal>1</Nominal>
<ParentCode>R01435 </ParentCode>
<ISO_Num_Code>440</ISO_Num_Code>
<ISO_Char_Code>LTL</ISO_Char_Code>
</Item>
<Item ID="R01436">
<Name>Литовский талон</Name>
<EngName>Lithuanian talon</EngName>
<Nominal>1</Nominal>
<ParentCode>R01435 </ParentCode>
<ISO_Num_Code/>
<ISO_Char_Code/>
</Item>
<Item ID="R01500">
<Name>Молдавский лей</Name>
<EngName>Moldova Lei</EngName>
<Nominal>10</Nominal>
<ParentCode>R01500 </ParentCode>
<ISO_Num_Code>498</ISO_Num_Code>
<ISO_Char_Code>MDL</ISO_Char_Code>
</Item>
<Item ID="R01510">
<Name>Немецкая марка</Name>
<EngName>Deutsche Mark</EngName>
<Nominal>1</Nominal>
<ParentCode>R01510 </ParentCode>
<ISO_Num_Code>276</ISO_Num_Code>
<ISO_Char_Code>DEM</ISO_Char_Code>
</Item>
<Item ID="R01510A">
<Name>Немецкая марка</Name>
<EngName>Deutsche Mark</EngName>
<Nominal>100</Nominal>
<ParentCode>R01510 </ParentCode>
<ISO_Num_Code>280</ISO_Num_Code>
<ISO_Char_Code>DEM</ISO_Char_Code>
</Item>
<Item ID="R01523">
<Name>Нидерландский гульден</Name>
<EngName>Netherlands Gulden</EngName>
<Nominal>100</Nominal>
<ParentCode>R01523 </ParentCode>
<ISO_Num_Code>528</ISO_Num_Code>
<ISO_Char_Code>NLG</ISO_Char_Code>
</Item>
<Item ID="R01530">
<Name>Новозеландский доллар</Name>
<EngName>New Zealand Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01530 </ParentCode>
<ISO_Num_Code>554</ISO_Num_Code>
<ISO_Char_Code>NZD</ISO_Char_Code>
</Item>
<Item ID="R01535">
<Name>Норвежская крона</Name>
<EngName>Norwegian Krone</EngName>
<Nominal>10</Nominal>
<ParentCode>R01535 </ParentCode>
<ISO_Num_Code>578</ISO_Num_Code>
<ISO_Char_Code>NOK</ISO_Char_Code>
</Item>
<Item ID="R01565">
<Name>Польский злотый</Name>
<EngName>Polish Zloty</EngName>
<Nominal>1</Nominal>
<ParentCode>R01565 </ParentCode>
<ISO_Num_Code>985</ISO_Num_Code>
<ISO_Char_Code>PLN</ISO_Char_Code>
</Item>
<Item ID="R01570">
<Name>Португальский эскудо</Name>
<EngName>Portuguese Escudo</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01570 </ParentCode>
<ISO_Num_Code>620</ISO_Num_Code>
<ISO_Char_Code>PTE</ISO_Char_Code>
</Item>
<Item ID="R01585">
<Name>Румынский лей</Name>
<EngName>Romanian Leu</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01585 </ParentCode>
<ISO_Num_Code>642</ISO_Num_Code>
<ISO_Char_Code>ROL</ISO_Char_Code>
</Item>
<Item ID="R01585F">
<Name>Румынский лей</Name>
<EngName>Romanian Leu</EngName>
<Nominal>1</Nominal>
<ParentCode>R01585 </ParentCode>
<ISO_Num_Code>946</ISO_Num_Code>
<ISO_Char_Code>RON</ISO_Char_Code>
</Item>
<Item ID="R01589">
<Name>СДР (специальные права заимствования)</Name>
<EngName>SDR</EngName>
<Nominal>1</Nominal>
<ParentCode>R01589 </ParentCode>
<ISO_Num_Code>960</ISO_Num_Code>
<ISO_Char_Code>XDR</ISO_Char_Code>
</Item>
<Item ID="R01625">
<Name>Сингапурский доллар</Name>
<EngName>Singapore Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01625 </ParentCode>
<ISO_Num_Code>702</ISO_Num_Code>
<ISO_Char_Code>SGD</ISO_Char_Code>
</Item>
<Item ID="R01665A">
<Name>Суринамский доллар</Name>
<EngName>Surinam Dollar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01665 </ParentCode>
<ISO_Num_Code>968</ISO_Num_Code>
<ISO_Char_Code>SRD</ISO_Char_Code>
</Item>
<Item ID="R01670">
<Name>Таджикский сомони</Name>
<EngName>Tajikistan Ruble</EngName>
<Nominal>10</Nominal>
<ParentCode>R01670 </ParentCode>
<ISO_Num_Code>972</ISO_Num_Code>
<ISO_Char_Code>TJS</ISO_Char_Code>
</Item>
<Item ID="R01675">
<Name>Таиландский бат</Name>
<EngName>Thai Baht</EngName>
<Nominal>10</Nominal>
<ParentCode>R01675 </ParentCode>
<ISO_Num_Code>764</ISO_Num_Code>
<ISO_Char_Code>THB</ISO_Char_Code>
</Item>
<Item ID="R01700J">
<Name>Турецкая лира</Name>
<EngName>Turkish Lira</EngName>
<Nominal>10</Nominal>
<ParentCode>R01700 </ParentCode>
<ISO_Num_Code>949</ISO_Num_Code>
<ISO_Char_Code>TRY</ISO_Char_Code>
</Item>
<Item ID="R01710">
<Name>Туркменский манат</Name>
<EngName>Turkmenistan Manat</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01710 </ParentCode>
<ISO_Num_Code>795</ISO_Num_Code>
<ISO_Char_Code>TMM</ISO_Char_Code>
</Item>
<Item ID="R01710A">
<Name>Новый туркменский манат</Name>
<EngName>New Turkmenistan Manat</EngName>
<Nominal>1</Nominal>
<ParentCode>R01710 </ParentCode>
<ISO_Num_Code>934</ISO_Num_Code>
<ISO_Char_Code>TMT</ISO_Char_Code>
</Item>
<Item ID="R01717">
<Name>Узбекский сум</Name>
<EngName>Uzbekistan Sum</EngName>
<Nominal>10000</Nominal>
<ParentCode>R01717 </ParentCode>
<ISO_Num_Code>860</ISO_Num_Code>
<ISO_Char_Code>UZS</ISO_Char_Code>
</Item>
<Item ID="R01720">
<Name>Украинская гривна</Name>
<EngName>Ukrainian Hryvnia</EngName>
<Nominal>10</Nominal>
<ParentCode>R01720 </ParentCode>
<ISO_Num_Code>980</ISO_Num_Code>
<ISO_Char_Code>UAH</ISO_Char_Code>
</Item>
<Item ID="R01720A">
<Name>Украинский карбованец</Name>
<EngName>Ukrainian Hryvnia</EngName>
<Nominal>1</Nominal>
<ParentCode>R01720 </ParentCode>
<ISO_Num_Code/>
<ISO_Char_Code/>
</Item>
<Item ID="R01740">
<Name>Финляндская марка</Name>
<EngName>Finnish Marka</EngName>
<Nominal>100</Nominal>
<ParentCode>R01740 </ParentCode>
<ISO_Num_Code>246</ISO_Num_Code>
<ISO_Char_Code>FIM</ISO_Char_Code>
</Item>
<Item ID="R01750">
<Name>Французский франк</Name>
<EngName>French Franc</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01750 </ParentCode>
<ISO_Num_Code>250</ISO_Num_Code>
<ISO_Char_Code>FRF</ISO_Char_Code>
</Item>
<Item ID="R01760">
<Name>Чешская крона</Name>
<EngName>Czech Koruna</EngName>
<Nominal>10</Nominal>
<ParentCode>R01760 </ParentCode>
<ISO_Num_Code>203</ISO_Num_Code>
<ISO_Char_Code>CZK</ISO_Char_Code>
</Item>
<Item ID="R01770">
<Name>Шведская крона</Name>
<EngName>Swedish Krona</EngName>
<Nominal>10</Nominal>
<ParentCode>R01770 </ParentCode>
<ISO_Num_Code>752</ISO_Num_Code>
<ISO_Char_Code>SEK</ISO_Char_Code>
</Item>
<Item ID="R01775">
<Name>Швейцарский франк</Name>
<EngName>Swiss Franc</EngName>
<Nominal>1</Nominal>
<ParentCode>R01775 </ParentCode>
<ISO_Num_Code>756</ISO_Num_Code>
<ISO_Char_Code>CHF</ISO_Char_Code>
</Item>
<Item ID="R01790">
<Name>ЭКЮ</Name>
<EngName>ECU</EngName>
<Nominal>1</Nominal>
<ParentCode>R01790 </ParentCode>
<ISO_Num_Code>954</ISO_Num_Code>
<ISO_Char_Code>XEU</ISO_Char_Code>
</Item>
<Item ID="R01795">
<Name>Эстонская крона</Name>
<EngName>Estonian Kroon</EngName>
<Nominal>10</Nominal>
<ParentCode>R01795 </ParentCode>
<ISO_Num_Code>233</ISO_Num_Code>
<ISO_Char_Code>EEK</ISO_Char_Code>
</Item>
<Item ID="R01805">
<Name>Югославский новый динар</Name>
<EngName>Yugoslavian Dinar</EngName>
<Nominal>1</Nominal>
<ParentCode>R01804 </ParentCode>
<ISO_Num_Code>890</ISO_Num_Code>
<ISO_Char_Code>YUN</ISO_Char_Code>
</Item>
<Item ID="R01805F">
<Name>Сербский динар</Name>
<EngName>Serbian Dinar</EngName>
<Nominal>100</Nominal>
<ParentCode>R01804 </ParentCode>
<ISO_Num_Code>941</ISO_Num_Code>
<ISO_Char_Code>RSD</ISO_Char_Code>
</Item>
<Item ID="R01810">
<Name>Южноафриканский рэнд</Name>
<EngName>S.African Rand</EngName>
<Nominal>10</Nominal>
<ParentCode>R01810 </ParentCode>
<ISO_Num_Code>710</ISO_Num_Code>
<ISO_Char_Code>ZAR</ISO_Char_Code>
</Item>
<Item ID="R01815">
<Name>Вон Республики Корея</Name>
<EngName>South Korean Won</EngName>
<Nominal>1000</Nominal>
<ParentCode>R01815 </ParentCode>
<ISO_Num_Code>410</ISO_Num_Code>
<ISO_Char_Code>KRW</ISO_Char_Code>
</Item>
<Item ID="R01820">
<Name>Японская иена</Name>
<EngName>Japanese Yen</EngName>
<Nominal>100</Nominal>
<ParentCode>R01820 </ParentCode>
<ISO_Num_Code>392</ISO_Num_Code>
<ISO_Char_Code>JPY</ISO_Char_Code>
</Item>
</Valuta>