You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6159 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1531 lines
42 KiB
ObjectPascal
1531 lines
42 KiB
ObjectPascal
// Upgraded to Delphi 2009: Sebastian Zierer
|
|
|
|
(* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is TurboPower SysTools
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* TurboPower Software
|
|
*
|
|
* Portions created by the Initial Developer are Copyright (C) 1996-2002
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* ***** END LICENSE BLOCK ***** *)
|
|
|
|
{*********************************************************}
|
|
{* SysTools: StMoney.pas 4.04 *}
|
|
{*********************************************************}
|
|
{* SysTools: Currency and Money Related Classes *}
|
|
{*********************************************************}
|
|
|
|
{$IFDEF FPC}
|
|
{$mode DELPHI}
|
|
{$ENDIF}
|
|
|
|
//{$include StDefine.inc}
|
|
|
|
unit StMoney;
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFNDEF FPC}
|
|
Windows,
|
|
{$ENDIF}
|
|
SysUtils, Classes,
|
|
|
|
StConst, StBase, StStrms, StDecMth, StIniStm;
|
|
|
|
|
|
type
|
|
{
|
|
; Layout of currency entries
|
|
[ISOCode]
|
|
Name=Country-Currency Name
|
|
ISOName=<ISO 4217 3 Letter Currency ID>
|
|
ISOCode=<ISO 4217 3 Digit Currency Number>
|
|
UnitMajor=<Major Currency Name>
|
|
UnitMinor=<Minor Currency Name>
|
|
Ratio=<ratio of minor currency to major>
|
|
}
|
|
|
|
TStCurrency = class(TObject)
|
|
{ representation of a national currency, based on ISO 4217 specification }
|
|
private
|
|
FName: String;
|
|
FISOCode: String;
|
|
FISOName: String;
|
|
FRatio: Integer;
|
|
FUnitMajor: String;
|
|
FUnitMinor: String;
|
|
|
|
public
|
|
{ Persistence and streaming methods }
|
|
procedure LoadFromList(List : TStrings);
|
|
procedure SaveToList(List : TStrings);
|
|
|
|
{ properties }
|
|
property ISOCode: String
|
|
read FISOCode write FISOCode;
|
|
property ISOName: String
|
|
read FISOName write FISOName;
|
|
property Name: String
|
|
read FName write FName;
|
|
property Ratio: Integer
|
|
read FRatio write FRatio;
|
|
property UnitMajor: String
|
|
read FUnitMajor write FUnitMajor;
|
|
property UnitMinor: String
|
|
read FUnitMinor write FUnitMinor;
|
|
end;
|
|
|
|
TStCurrencyList = class (TObject)
|
|
{ collection of national currencies }
|
|
private
|
|
FItems: TStringList;
|
|
protected {private}
|
|
function GetCount: Integer;
|
|
function GetCurrency(const ISOName : String): TStCurrency;
|
|
function GetItem(Index : Integer): TStCurrency;
|
|
procedure SetCurrency(const ISOName : String; Value: TStCurrency);
|
|
procedure SetItem(Index : Integer; Value: TStCurrency);
|
|
|
|
procedure FreeCurrencyByIndex(Index : Integer);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
{ Access and Update Methods }
|
|
procedure Add(ACurrency : TStCurrency);
|
|
procedure Clear;
|
|
function Contains(ACurrency : TStCurrency): Boolean;
|
|
function ContainsName(const ISOName : String): Boolean;
|
|
procedure Delete(const ISOName: String);
|
|
function IndexOf(const ISOName : String) : Integer;
|
|
|
|
{ Persistence and streaming methods }
|
|
procedure LoadFromFile(const AFileName: TFileName);
|
|
procedure LoadFromStream(AStream: TStream);
|
|
procedure SaveToFile(const AFileName: TFileName);
|
|
procedure SaveToStream(AStream: TStream);
|
|
|
|
{ properties }
|
|
property Count : Integer
|
|
read GetCount;
|
|
property Currencies[const ISOName : String]: TStCurrency
|
|
read GetCurrency write SetCurrency;
|
|
property Items[Index : Integer] : TStCurrency
|
|
read GetItem write SetItem; default;
|
|
end;
|
|
|
|
|
|
{
|
|
Conversion Methods
|
|
===================
|
|
When converting money of one currency into money of another currency, three
|
|
conversion methods are commonly encountered:
|
|
|
|
1)
|
|
"Triangular": the source currency amount is converted to an intermediate
|
|
currency amount, then the intermediate currency amount is converted to
|
|
the target amount.
|
|
|
|
Note: This is the method required by members of the European Monetary
|
|
Union (EMU), for converting among national currencies that are transitioning
|
|
to the Euro; the Euro should be used as the Intermediate currency for such
|
|
conversions.
|
|
|
|
2)
|
|
"Multiply" the source currency amount is multiplied by a conversion Rate
|
|
to obtain the target currency amount.
|
|
|
|
3)
|
|
"Divide" the source currency amount is divided by a conversion Rate to
|
|
obtain the target currency amount.
|
|
}
|
|
TStConversionType = (ctUnknown, ctTriangular, ctMultiply, ctDivide);
|
|
|
|
TStGetRateUpdateEvent = procedure (Sender: TObject; NewRate : TStDecimal;
|
|
var NewDate : TDateTime) of object;
|
|
{
|
|
; Layout of exchange entries
|
|
[SRC:TRG]
|
|
source=SRC
|
|
target=TRG
|
|
; empty/ignored if not a triangular exchange
|
|
intermediate=XXX
|
|
rate=xxx
|
|
; error if tri and intermediate not set
|
|
type=<tri|mul|div>
|
|
date=<date>
|
|
}
|
|
|
|
TStExchangeRate = class (TObject)
|
|
{ particular Exchange Rate between two currencies }
|
|
private
|
|
FRate: TStDecimal;
|
|
FSource: String;
|
|
FTarget : String;
|
|
FIntermediate : String;
|
|
FConversionType : TStConversionType;
|
|
FDateUpdated : TDateTime;
|
|
FOnGetRateUpdate: TStGetRateUpdateEvent;
|
|
procedure SetRate(const Value: TStDecimal);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
{ Access and Update Methods }
|
|
procedure Assign(ARate : TStExchangeRate);
|
|
procedure Clear;
|
|
procedure Convert(Amount, Result: TStDecimal);
|
|
function Equals(aRate : TStExchangeRate) : Boolean;
|
|
function IsValid : Boolean;
|
|
function SameSourceAndTarget(aRate : TStExchangeRate) : Boolean;
|
|
procedure Update;
|
|
|
|
{ Persistence and streaming methods }
|
|
procedure LoadFromList(List : TStrings);
|
|
procedure SaveToList(List : TStrings);
|
|
|
|
{ properties }
|
|
property ConversionType : TStConversionType
|
|
read FConversionType write FConversionType;
|
|
property DateUpdated : TDateTime
|
|
read FDateUpdated write FDateUpdated;
|
|
property Intermediate : String
|
|
read FIntermediate write FIntermediate;
|
|
property Rate : TStDecimal
|
|
read FRate write SetRate;
|
|
property Source : String
|
|
read FSource write FSource;
|
|
property Target : String
|
|
read FTarget write FTarget;
|
|
|
|
{ events }
|
|
property OnGetRateUpdate : TStGetRateUpdateEvent
|
|
read FOnGetRateUpdate write FOnGetRateUpdate;
|
|
end;
|
|
|
|
TStExchangeRateList = class (TObject)
|
|
{ collection of currency conversions (TStExchangeRate) }
|
|
private
|
|
FRates : TStringList;
|
|
protected {private}
|
|
procedure DeleteRate(Index: Integer);
|
|
function GetCount: Integer;
|
|
function GetRate(const Source, Target: String): TStExchangeRate;
|
|
function GetItem(Index: Integer): TStExchangeRate;
|
|
function MakeEntry(const Source, Target: String): String; virtual;
|
|
|
|
procedure ConvertPrim(const aSource, aTarget : string;
|
|
aAmount : TStDecimal;
|
|
aAllowTriangular : boolean);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
{ Access and Update Methods }
|
|
procedure Add(ARate : TStExchangeRate);
|
|
procedure AddByValues(const Source, Target, Intermediate: String;
|
|
Rate: Double; ConversionType: TStConversionType; DateUpdated: TDateTime);
|
|
procedure Assign(AList : TStExchangeRateList);
|
|
procedure Clear;
|
|
function Contains(ARate : TStExchangeRate) : Boolean;
|
|
function ContainsByName(const Source, Target : String) : Boolean;
|
|
procedure Convert(const Source, Target : String;
|
|
Amount, Result : TStDecimal);
|
|
procedure Delete(ARate : TStExchangeRate);
|
|
procedure DeleteByName(const Source, Target : String);
|
|
procedure UpdateRate(const Source, Target : String; Rate : TStDecimal);
|
|
|
|
{ Persistence and streaming methods }
|
|
procedure LoadFromFile(const AFileName: TFileName);
|
|
procedure LoadFromStream(AStream: TStream);
|
|
procedure SaveToFile(const AFileName: TFileName);
|
|
procedure SaveToStream(AStream: TStream);
|
|
|
|
{ properties }
|
|
property Count : Integer
|
|
read GetCount;
|
|
{ Returns the number of exchange rates in this table. }
|
|
property Items[Index : Integer] : TStExchangeRate
|
|
read GetItem;
|
|
{ access to all of the exchange rates in the collection by numeric index }
|
|
property Rates[const Source, Target : String] : TStExchangeRate
|
|
read GetRate;
|
|
{ access to all of the exchange rates in the collection by Source and Target }
|
|
end;
|
|
|
|
TStMoney = class (TObject)
|
|
{ representation of an amount of Currency and operations on same }
|
|
private
|
|
FAmount : TStDecimal;
|
|
FCurrency : String;
|
|
FExchangeRates : TStExchangeRateList;
|
|
|
|
function GetAsFloat: Double;
|
|
function GetAsString: String;
|
|
procedure SetAmount(const Value: TStDecimal);
|
|
procedure SetAsFloat(const Value: Double);
|
|
procedure SetAsString(const Value: String);
|
|
procedure Validate(Source, Operand, Result: TStMoney);
|
|
function ValidateCurrencies(Source, Dest: TStMoney) : Boolean;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Assign(AMoney : TStMoney);
|
|
|
|
{ basic math operations }
|
|
procedure Abs(Result : TStMoney);
|
|
procedure Add(Addend, Sum : TStMoney);
|
|
procedure Divide(Divisor : Double; Quotient : TStMoney);
|
|
procedure DivideByDecimal(Divisor : TStDecimal; Quotient : TStMoney);
|
|
procedure Multiply(Multiplier : Double; Product : TStMoney);
|
|
procedure MultiplyByDecimal(Multiplier : TStDecimal; Product : TStMoney);
|
|
procedure Negate(Result : TStMoney);
|
|
procedure Subtract(Subtrahend, Remainder : TStMoney);
|
|
|
|
{ logical comparisons }
|
|
function Compare(CompareTo : TStMoney): Integer;
|
|
function IsEqual(AMoney : TStMoney): Boolean;
|
|
function IsGreaterThan(AMoney : TStMoney): Boolean;
|
|
function IsGreaterThanOrEqual(AMoney : TStMoney): Boolean;
|
|
function IsLessThan(AMoney : TStMoney): Boolean;
|
|
function IsLessThanOrEqual(AMoney : TStMoney): Boolean;
|
|
function IsNegative: Boolean;
|
|
function IsNotEqual(AMoney : TStMoney): Boolean;
|
|
function IsPositive: Boolean;
|
|
function IsZero: Boolean;
|
|
|
|
{ Conversion Methods }
|
|
procedure Convert(const Target : String; Result : TStMoney);
|
|
procedure Round(Method : TStRoundMethod; Decimals : Integer; Result : TStMoney);
|
|
{ See definition of TStRoundMethod in the StDecMth unit for more
|
|
information on rounding }
|
|
|
|
{ properties }
|
|
property Amount: TStDecimal
|
|
read FAmount write SetAmount;
|
|
property AsFloat: Double
|
|
read GetAsFloat write SetAsFloat;
|
|
property AsString: String
|
|
read GetAsString write SetAsString;
|
|
property Currency: String
|
|
read FCurrency write FCurrency;
|
|
property ExchangeRates : TStExchangeRateList
|
|
read FExchangeRates write FExchangeRates;
|
|
end;
|
|
|
|
implementation
|
|
|
|
var
|
|
ExchBaseDate : TDateTime; // the base date for exchange rates
|
|
|
|
{ TStCurrency }
|
|
|
|
procedure TStCurrency.LoadFromList(List : TStrings);
|
|
{
|
|
assign currency properties from a set of <Name>=<Value> pairs
|
|
|
|
BuildItem expects data in the form:
|
|
|
|
Name=Country-Currency Name
|
|
ISOName=<ISO 4217 3 Letter Currency ID>
|
|
ISOCode=<ISO 4217 3 Digit Currency Number>
|
|
UnitMajor=<Major Currency Name>
|
|
UnitMinor=<Minor Currency Name>
|
|
Ratio=<ratio of minor currency to major>
|
|
}
|
|
begin
|
|
if Assigned(List) then begin
|
|
FName := List.Values['Name'];
|
|
FISOCode := List.Values['ISOCode'];
|
|
FISOName := List.Values['ISOName'];
|
|
FUnitMajor := List.Values['UnitMajor'];
|
|
FUnitMinor := List.Values['UnitMinor'];
|
|
FRatio := StrToIntDef(List.Values['Ratio'], 100);
|
|
end;
|
|
end;
|
|
|
|
procedure TStCurrency.SaveToList(List : TStrings);
|
|
{ write Currency data to <Name>=<Value> pairs for persistence }
|
|
begin
|
|
if Assigned(List) then begin
|
|
List.Clear;
|
|
List.Add('Name=' + FName);
|
|
List.Add('ISOCode=' + FISOCode);
|
|
List.Add('ISOName=' + FISOName);
|
|
List.Add('UnitMajor=' + FUnitMajor);
|
|
List.Add('UnitMinor=' + FUnitMinor);
|
|
List.Add('Ratio=' + IntToStr(FRatio));
|
|
end;
|
|
end;
|
|
|
|
{ TStCurrencyList }
|
|
|
|
constructor TStCurrencyList.Create;
|
|
begin
|
|
inherited Create;
|
|
FItems := TStringList.Create;
|
|
FItems.Sorted := True;
|
|
FItems.Duplicates := dupIgnore;
|
|
end;
|
|
|
|
destructor TStCurrencyList.Destroy;
|
|
begin
|
|
Clear;
|
|
FItems.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TStCurrencyList.Add(ACurrency: TStCurrency);
|
|
{ add a new currency to the list }
|
|
begin
|
|
if Assigned(ACurrency) then
|
|
FItems.AddObject(ACurrency.ISOName, ACurrency);
|
|
end;
|
|
|
|
procedure TStCurrencyList.Clear;
|
|
{ Clear the list of currencies }
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := Pred(FItems.Count) downto 0 do
|
|
FreeCurrencyByIndex(i);
|
|
end;
|
|
|
|
function TStCurrencyList.Contains(ACurrency: TStCurrency): Boolean;
|
|
{ returns true if there's an entry for such a currency }
|
|
begin
|
|
Result := False;
|
|
if Assigned(ACurrency) then
|
|
Result := FItems.IndexOf(ACurrency.ISOName) >= 0;
|
|
end;
|
|
|
|
function TStCurrencyList.ContainsName(const ISOName: String): Boolean;
|
|
{ returns true if there's an entry for such a currency ID }
|
|
begin
|
|
Result := FItems.IndexOf(ISOName) >= 0;
|
|
end;
|
|
|
|
procedure TStCurrencyList.Delete(const ISOName: String);
|
|
{ delete the requested currency from the list }
|
|
begin
|
|
FreeCurrencyByIndex(FItems.IndexOf(ISOName));
|
|
end;
|
|
|
|
procedure TStCurrencyList.FreeCurrencyByIndex(Index: Integer);
|
|
{ release a currency by the requested numeric index in the list }
|
|
begin
|
|
{ if index in range }
|
|
if (0 <= Index) and (Index < FItems.Count) then begin
|
|
{ free StCurrency data at that index }
|
|
(FItems.Objects[Index] as TStCurrency).Free;
|
|
{ delete item from list }
|
|
FItems.Delete(Index);
|
|
end;
|
|
{ else, item doesn't exist, so do nothing }
|
|
end;
|
|
|
|
function TStCurrencyList.GetCount : Integer;
|
|
{ just return count of maintained items }
|
|
begin
|
|
Result := FItems.Count;
|
|
end;
|
|
|
|
function TStCurrencyList.GetCurrency(const ISOName: String): TStCurrency;
|
|
{
|
|
return reference to requested currency item indexed by ISOName
|
|
returns nil if item doesn't exist
|
|
}
|
|
var
|
|
Index : Integer;
|
|
begin
|
|
{ find index of item }
|
|
Index := FItems.IndexOf(ISOName);
|
|
{ return item as a TStCurrency reference, or nil if it wasn't found }
|
|
if (Index >= 0) then
|
|
Result := GetItem(Index)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TStCurrencyList.GetItem(Index : Integer): TStCurrency;
|
|
{
|
|
return reference to requested currency item indexed by position in list
|
|
returns nil if item doesn't exist
|
|
}
|
|
begin
|
|
if not ((0 <= Index) and (Index < FItems.Count)) then
|
|
raise EStException.CreateResFmtTP(stscBadIndex, [IntToStr(Index)], 0);
|
|
|
|
Result := (FItems.Objects[Index] as TStCurrency);
|
|
end;
|
|
|
|
function TStCurrencyList.IndexOf(const ISOName: String): Integer;
|
|
{
|
|
locate index of requested item in list,
|
|
returns -1 if item doesn't exist
|
|
}
|
|
begin
|
|
Result := FItems.IndexOf(ISOName);
|
|
end;
|
|
|
|
procedure TStCurrencyList.LoadFromFile(const AFileName: TFileName);
|
|
var
|
|
FS : TFileStream;
|
|
begin
|
|
FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(FS);
|
|
finally
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStCurrencyList.LoadFromStream(AStream : TStream);
|
|
var
|
|
IniStr : TStIniStream;
|
|
Currencies, Section : TStrings;
|
|
ACurrency : TStCurrency;
|
|
i : Integer;
|
|
begin
|
|
{clear out the current currency items}
|
|
Clear;
|
|
|
|
IniStr := nil;
|
|
Currencies := nil;
|
|
Section := nil;
|
|
ACurrency := nil;
|
|
try
|
|
IniStr := TStIniStream.Create(AStream);
|
|
Currencies := TStringList.Create;
|
|
Section := TStringList.Create;
|
|
{ create an "index" of the sections }
|
|
IniStr.ReadSections(Currencies);
|
|
|
|
{ read a currency definition }
|
|
for i := 0 to Pred(Currencies.Count) do begin
|
|
{ get settings as .INI style items }
|
|
IniStr.ReadSectionValues(Currencies[i], Section);
|
|
|
|
{ create a new currency item }
|
|
ACurrency := TStCurrency.Create;
|
|
|
|
{ set its properties }
|
|
ACurrency.LoadFromList(Section);
|
|
|
|
{ add it to the list }
|
|
FItems.AddObject(ACurrency.ISOName, ACurrency);
|
|
ACurrency := nil;
|
|
end;
|
|
finally
|
|
IniStr.Free;
|
|
Section.Free;
|
|
Currencies.Free;
|
|
// note: this only does something if either the LoadFromList or
|
|
// AddObject calls failed
|
|
ACurrency.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStCurrencyList.SaveToFile(const AFileName: TFileName);
|
|
var
|
|
FS : TFileStream;
|
|
begin
|
|
if not FileExists(AFileName) then begin
|
|
FS := TFileStream.Create(AFileName, fmCreate);
|
|
FS.Free;
|
|
end;
|
|
|
|
FS := TFileStream.Create(AFileName, fmOpenWrite or fmShareDenyNone);
|
|
try
|
|
SaveToStream(FS);
|
|
finally
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStCurrencyList.SaveToStream(AStream : TStream);
|
|
var
|
|
IniStr : TStIniStream;
|
|
Strs : TStringList;
|
|
i : Integer;
|
|
begin
|
|
IniStr := nil;
|
|
Strs := nil;
|
|
try
|
|
IniStr := TStIniStream.Create(AStream);
|
|
Strs := TStringList.Create;
|
|
for i := 0 to Pred(FItems.Count) do begin
|
|
{ clear the string list to contain the ccy definition }
|
|
Strs.Clear;
|
|
{ get item properties as string list }
|
|
(FItems.Objects[i] as TStCurrency).SaveToList(Strs);
|
|
{ add new section to .INI data }
|
|
IniStr.WriteSection(FItems[i], Strs);
|
|
end;
|
|
finally
|
|
Strs.Free;
|
|
IniStr.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStCurrencyList.SetCurrency(const ISOName: String;
|
|
Value: TStCurrency);
|
|
var
|
|
Idx : Integer;
|
|
begin
|
|
{ locate item }
|
|
Idx := FItems.IndexOf(ISOName);
|
|
if (Idx >= 0) then
|
|
SetItem(Idx, Value);
|
|
end;
|
|
|
|
procedure TStCurrencyList.SetItem(Index : Integer;
|
|
Value: TStCurrency);
|
|
begin
|
|
if not ((0 <= Index) and (Index < FItems.Count)) then
|
|
raise EStException.CreateResFmtTP(stscBadIndex, [IntToStr(Index)], 0);
|
|
|
|
if Assigned(Value) then begin
|
|
{ release current currency info }
|
|
(FItems.Objects[Index] as TStCurrency).Free;
|
|
{ replace with new info }
|
|
FItems.Objects[Index] := Value;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TStMoney }
|
|
|
|
constructor TStMoney.Create;
|
|
begin
|
|
inherited Create;
|
|
FAmount := TStDecimal.Create;
|
|
end;
|
|
|
|
destructor TStMoney.Destroy;
|
|
begin
|
|
FAmount.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TStMoney.Abs(Result : TStMoney);
|
|
{ Returns a new money which has the absolute value of this money's amount. }
|
|
begin
|
|
Result.Assign(Self);
|
|
Result.Amount.Abs;
|
|
end;
|
|
|
|
procedure TStMoney.Add(Addend, Sum : TStMoney);
|
|
begin
|
|
Validate(Self, Addend, Sum);
|
|
Sum.Assign(Self);
|
|
Sum.Amount.Add(Addend.Amount);
|
|
end;
|
|
|
|
procedure TStMoney.Assign(AMoney : TStMoney);
|
|
begin
|
|
if Assigned(AMoney) then begin
|
|
Amount.Assign(AMoney.Amount);
|
|
Currency := AMoney.Currency;
|
|
ExchangeRates := AMoney.ExchangeRates;
|
|
end;
|
|
end;
|
|
|
|
function TStMoney.Compare(CompareTo : TStMoney): Integer;
|
|
{
|
|
Compares this money to the specified money.
|
|
|
|
Returns <0 if this money is less than the other money, 0 if they are equal,
|
|
and >0 if it is greater
|
|
|
|
Note: Currencies must also be the same
|
|
}
|
|
begin
|
|
Validate(Self, CompareTo, Self);
|
|
Result := Amount.Compare(CompareTo.Amount);
|
|
end;
|
|
|
|
procedure TStMoney.Convert(const Target : String; Result : TStMoney);
|
|
{
|
|
Converts the value to a different currency, utilizes TStExchangeRateList
|
|
}
|
|
begin
|
|
{ check that exchange rates are available }
|
|
if not Assigned(ExchangeRates) then
|
|
raise EStException.CreateResTP(stscMoneyNoExchangeRatesAvail, 0);
|
|
|
|
{ check validity of operands and result }
|
|
if not Assigned(Result) then
|
|
raise EStException.CreateResTP(stscMoneyNilResult, 0);
|
|
|
|
Result.Assign(Self);
|
|
ExchangeRates.Convert(Currency, Target, Amount, Result.Amount);
|
|
end;
|
|
|
|
procedure TStMoney.DivideByDecimal(Divisor : TStDecimal; Quotient : TStMoney);
|
|
{ Returns a new money which is the quotient of the money divided by
|
|
the decimal divisor. }
|
|
begin
|
|
if not Assigned(Divisor) then
|
|
raise EStException.CreateResTP(stscMoneyNilParameter, 0);
|
|
|
|
if not Assigned(Quotient) then
|
|
raise EStException.CreateResTP(stscMoneyNilResult, 0);
|
|
|
|
Quotient.Assign(Self);
|
|
Quotient.Amount.Divide(Divisor);
|
|
end;
|
|
|
|
procedure TStMoney.Divide(Divisor : Double; Quotient : TStMoney);
|
|
{ Returns a new money which is the quotient of the money divided by
|
|
the floating point divisor. }
|
|
var
|
|
DecDiv : TStDecimal;
|
|
begin
|
|
DecDiv := TStDecimal.Create;
|
|
try
|
|
DecDiv.AssignFromFloat(Divisor);
|
|
DivideByDecimal(DecDiv, Quotient);
|
|
finally
|
|
DecDiv.Free;
|
|
end;
|
|
end;
|
|
|
|
function TStMoney.GetAsFloat: Double;
|
|
{ return money amount as a Floating point value }
|
|
begin
|
|
Result := Amount.AsFloat;
|
|
end;
|
|
|
|
function TStMoney.GetAsString: String;
|
|
{ return money amount as a string }
|
|
begin
|
|
Result := Amount.AsString;
|
|
end;
|
|
|
|
function TStMoney.IsEqual(AMoney : TStMoney): Boolean;
|
|
{ Returns true if this money and the specified money are equal }
|
|
begin
|
|
Result := Compare(AMoney) = 0;
|
|
end;
|
|
|
|
function TStMoney.IsGreaterThan(AMoney : TStMoney): Boolean;
|
|
{ Returns true if this money's amount is greater than that of the specified money. }
|
|
begin
|
|
Result := Compare(AMoney) > 0;
|
|
end;
|
|
|
|
function TStMoney.IsGreaterThanOrEqual(AMoney : TStMoney): Boolean;
|
|
{ Returns true if this money's amount is greater than or equal to the specified money. }
|
|
begin
|
|
Result := Compare(AMoney) >= 0;
|
|
end;
|
|
|
|
function TStMoney.IsPositive : Boolean;
|
|
{ Returns true if this money's amount is greater than zero. }
|
|
begin
|
|
Result := Amount.IsPositive;
|
|
end;
|
|
|
|
function TStMoney.IsZero: Boolean;
|
|
{ Returns true if this money's amount is equal to zero. }
|
|
begin
|
|
Result := Amount.IsZero;
|
|
end;
|
|
|
|
function TStMoney.IsLessThan(AMoney : TStMoney): Boolean;
|
|
{ Returns true if this money's amount is less than that of the specified money. }
|
|
begin
|
|
Result := Compare(AMoney) < 0;
|
|
end;
|
|
|
|
function TStMoney.IsLessThanOrEqual(AMoney : TStMoney): Boolean;
|
|
{ Returns true if this money's amount is less than or equal to that of the specified money. }
|
|
begin
|
|
Result := Compare(AMoney) <= 0;
|
|
end;
|
|
|
|
function TStMoney.IsNegative: Boolean;
|
|
{ Returns true if this money's amount is less than zero. }
|
|
begin
|
|
Result := Amount.IsNegative;
|
|
end;
|
|
|
|
function TStMoney.IsNotEqual(AMoney : TStMoney): Boolean;
|
|
{ Returns true if this money and the specified money are not equal }
|
|
begin
|
|
Result := Compare(AMoney) <> 0;
|
|
end;
|
|
|
|
procedure TStMoney.MultiplyByDecimal(Multiplier : TStDecimal;
|
|
Product : TStMoney);
|
|
{ Returns a new money which is the product of the money and the decimal value. }
|
|
begin
|
|
if not Assigned(Multiplier) then
|
|
raise EStException.CreateResTP(stscMoneyNilParameter, 0);
|
|
|
|
if not Assigned(Product) then
|
|
raise EStException.CreateResTP(stscMoneyNilResult, 0);
|
|
|
|
Product.Assign(Self);
|
|
Product.Amount.Multiply(Multiplier);
|
|
end;
|
|
|
|
procedure TStMoney.Multiply(Multiplier : Double; Product : TStMoney);
|
|
{ Returns a new money which is the product of the money and the floating point value. }
|
|
var
|
|
MulDec : TStDecimal;
|
|
begin
|
|
MulDec := TStDecimal.Create;
|
|
try
|
|
MulDec.AssignFromFloat(Multiplier);
|
|
MultiplyByDecimal(MulDec, Product);
|
|
finally
|
|
MulDec.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStMoney.Negate(Result : TStMoney);
|
|
{ Returns a new money which is the negation of this money's amount. }
|
|
begin
|
|
if not Assigned(Result) then
|
|
raise EStException.CreateResTP(stscMoneyNilResult, 0);
|
|
|
|
Result.Assign(Self);
|
|
Result.Amount.ChangeSign;
|
|
end;
|
|
|
|
procedure TStMoney.Round(Method : TStRoundMethod; Decimals : Integer; Result : TStMoney);
|
|
{
|
|
Returns a new money with the rounded value of this money using the specified accuracy.
|
|
and using the specified rounding method
|
|
|
|
See definition of TStRoundMethod in the StDecMth unit for more
|
|
information on rounding
|
|
}
|
|
begin
|
|
if not Assigned(Result) then
|
|
raise EStException.CreateResTP(stscMoneyNilResult, 0);
|
|
|
|
Result.Assign(Self);
|
|
Result.Amount.Round(Method, Decimals);
|
|
end;
|
|
|
|
procedure TStMoney.SetAmount(const Value: TStDecimal);
|
|
begin
|
|
Amount.Assign(Value);
|
|
end;
|
|
|
|
procedure TStMoney.SetAsFloat(const Value: Double);
|
|
begin
|
|
Amount.AssignFromFloat(Value);
|
|
end;
|
|
|
|
procedure TStMoney.SetAsString(const Value: String);
|
|
begin
|
|
Amount.AsString := Value;
|
|
end;
|
|
|
|
procedure TStMoney.Subtract(Subtrahend, Remainder : TStMoney);
|
|
{ Returns a new money which is the difference between this money and the given money. }
|
|
begin
|
|
Validate(Self, Subtrahend, Remainder);
|
|
Remainder.Assign(Self);
|
|
Remainder.Amount.Subtract(Subtrahend.Amount);
|
|
end;
|
|
|
|
function TStMoney.ValidateCurrencies(Source, Dest : TStMoney) : Boolean;
|
|
begin
|
|
Result := Source.Currency = Dest.Currency;
|
|
end;
|
|
|
|
procedure TStMoney.Validate(Source, Operand, Result : TStMoney);
|
|
begin
|
|
{ check validity of operands and result }
|
|
if not Assigned(Source) or not Assigned(Operand) then
|
|
raise EStException.CreateResTP(stscMoneyNilParameter, 0);
|
|
|
|
if not Assigned(Result) then
|
|
raise EStException.CreateResTP(stscMoneyNilResult, 0);
|
|
|
|
if not ValidateCurrencies(Source, Operand) then
|
|
raise EStException.CreateResTP(stscMoneyCurrenciesNotMatch, 0);
|
|
end;
|
|
|
|
{ TStExchangeRate }
|
|
|
|
constructor TStExchangeRate.Create;
|
|
begin
|
|
inherited Create;
|
|
FRate := TStDecimal.Create;
|
|
Clear;
|
|
end;
|
|
|
|
destructor TStExchangeRate.Destroy;
|
|
begin
|
|
FRate.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TStExchangeRate.Assign(ARate: TStExchangeRate);
|
|
begin
|
|
if Assigned(ARate) then begin
|
|
Source := ARate.Source;
|
|
Target := ARate.Target;
|
|
Intermediate := ARate.Intermediate;
|
|
ConversionType := ARate.ConversionType;
|
|
DateUpdated := ARate.DateUpdated;
|
|
Rate.Assign(ARate.Rate);
|
|
end else
|
|
begin
|
|
Clear;
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRate.Clear;
|
|
{ clear item fields }
|
|
begin
|
|
FSource := '';
|
|
FTarget := '';
|
|
FIntermediate := '';
|
|
FConversionType := ctMultiply;
|
|
FDateUpdated := ExchBaseDate;
|
|
FRate.SetToOne;
|
|
end;
|
|
|
|
procedure TStExchangeRate.Convert(Amount, Result: TStDecimal);
|
|
{ convert supplied amount using current ConversionType and Exchange Rate }
|
|
begin
|
|
{the parameters must be present}
|
|
if not Assigned(Amount) or not Assigned(Result) then
|
|
raise EStException.CreateResTP(stscMoneyNilParameter, 0);
|
|
|
|
{the exchange rate must be valid}
|
|
if not IsValid then
|
|
raise EStException.CreateResTP(stscMoneyInvalidExchRate, 0);
|
|
|
|
{set the result equal to the amount prior to converting it}
|
|
Result.Assign(Amount);
|
|
|
|
case ConversionType of
|
|
{ multiplication conversion }
|
|
ctMultiply :
|
|
begin
|
|
Result.Multiply(Rate);
|
|
end;
|
|
|
|
{ division conversion }
|
|
ctDivide :
|
|
begin
|
|
Result.Divide(Rate);
|
|
end;
|
|
|
|
{ triangular conversion }
|
|
ctTriangular :
|
|
begin
|
|
{this can't be done by a single exchange rate}
|
|
raise EStException.CreateResTP(stscMoneyInvalidTriangleExchange, 0);
|
|
end;
|
|
|
|
else
|
|
raise EStException.CreateResTP(stscMoneyInvalidExchangeParams, 0);
|
|
end; { case }
|
|
end;
|
|
|
|
function TStExchangeRate.Equals(aRate: TStExchangeRate): Boolean;
|
|
{
|
|
Returns true if this exchange rate and specified exchange rate have
|
|
identical Exchange types, Source currencies, Target currencies,
|
|
and conversion Rates or are both Triangular exchanges with the same
|
|
Source, Target, and Intermediate currencies
|
|
}
|
|
var
|
|
CurrenciesMatch, TypesMatch : Boolean;
|
|
begin
|
|
Result := False;
|
|
if not Assigned(aRate) then Exit;
|
|
|
|
{ check if currencies match }
|
|
CurrenciesMatch := (AnsiCompareText(Source, aRate.Source) = 0) and
|
|
(AnsiCompareText(Target, aRate.Target) = 0);
|
|
|
|
{ check if exchange types match }
|
|
TypesMatch := (ConversionType = aRate.ConversionType);
|
|
|
|
if TypesMatch and CurrenciesMatch then
|
|
case ConversionType of
|
|
ctTriangular : { both triangular }
|
|
{ equal if same intermediate currency }
|
|
Result := (FIntermediate = aRate.FIntermediate);
|
|
|
|
ctMultiply,
|
|
ctDivide : { both multiply or divide }
|
|
{ equal if same conversion rate }
|
|
Result := (Rate.Compare(aRate.Rate) = 0);
|
|
else
|
|
raise EStException.CreateResTP(stscMoneyInvalidExchangeParams, 0);
|
|
end; { case }
|
|
end;
|
|
|
|
function TStExchangeRate.IsValid: Boolean;
|
|
{
|
|
Checks to see if this exchange rate has its source, target and Rate
|
|
fields set to non-default values, or if a Triangular exchange, that
|
|
the intermediate currency is set
|
|
}
|
|
begin
|
|
{assume the exchange rate is invalid}
|
|
Result := false;
|
|
|
|
{the source cannot be empty}
|
|
if (Source = '') then
|
|
Exit;
|
|
|
|
{the target cannot be empty}
|
|
if (Target = '') then
|
|
Exit;
|
|
|
|
{the source and target must be different}
|
|
if (AnsiCompareText(Source, Target) = 0) then
|
|
Exit;
|
|
|
|
{for a multiply/divide conversion, the rate must be > 0.0}
|
|
if (ConversionType = ctMultiply) or (ConversionType = ctDivide) then begin
|
|
Result := FRate.IsPositive;
|
|
Exit;
|
|
end;
|
|
|
|
{for a triangular conversion, the intermediate currency must be set
|
|
and cannot be equal to either Source or Target to avoid infinite
|
|
loops in TStExchangeList.Convert <g>}
|
|
if (ConversionType = ctTriangular) then begin
|
|
if (Intermediate = '') then
|
|
Exit;
|
|
if (AnsiCompareText(Source, Intermediate) = 0) then
|
|
Exit;
|
|
if (AnsiCompareText(Target, Intermediate) = 0) then
|
|
Exit;
|
|
Result := true;
|
|
Exit;
|
|
end;
|
|
|
|
{otherwise the exchange rate is invalid}
|
|
end;
|
|
|
|
function MakeXChgStr(ConversionType : TStConversionType) : String;
|
|
{ convert TStConversionType to string for persistence }
|
|
begin
|
|
case ConversionType of
|
|
ctTriangular : Result := 'tri';
|
|
ctMultiply : Result := 'mul';
|
|
ctDivide : Result := 'div';
|
|
else
|
|
raise Exception.Create('Unknown conversion type');
|
|
end; { case }
|
|
end;
|
|
|
|
function MakeXChg(const XchStr : String) : TStConversionType;
|
|
{ convert persistence string to TStConversionType }
|
|
begin
|
|
if (AnsiCompareText(XchStr, 'mul') = 0) then
|
|
Result := ctMultiply
|
|
else if (AnsiCompareText(XchStr, 'div') = 0) then
|
|
Result := ctDivide
|
|
else if (AnsiCompareText(XchStr, 'tri') = 0) then
|
|
Result := ctTriangular
|
|
else begin
|
|
raise Exception.Create('Unknown conversion type in INI file');
|
|
Result := ctUnknown;
|
|
end;
|
|
end;
|
|
|
|
procedure ReplaceCh(var S : String; aFromCh : Char; aToCh : Char);
|
|
var
|
|
i : integer;
|
|
begin
|
|
{replace the first occurrence of aFromCh with aToCh in string S}
|
|
for i := 0 to length(S) do
|
|
if (S[i] = aFromCh) then begin
|
|
S[i] := aToCh;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRate.LoadFromList(List: TStrings);
|
|
{
|
|
set item properties from Exchange Rate data
|
|
expects data in the format:
|
|
|
|
source=<source currency>
|
|
target=<target currency>
|
|
intermediate=<intermediate currency>
|
|
rate=<exchange rate>
|
|
type=<tri|mul|div>
|
|
date=<date of setting>
|
|
}
|
|
var
|
|
Str : String;
|
|
DayCount : integer;
|
|
ec : integer;
|
|
begin
|
|
if Assigned(List) then begin
|
|
Clear;
|
|
FSource := List.Values['source'];
|
|
FTarget := List.Values['target'];
|
|
FIntermediate := List.Values['intermediate'];
|
|
FConversionType := MakeXChg(List.Values['type']);
|
|
|
|
Str := List.Values['date'];
|
|
Val(Str, DayCount, ec);
|
|
if (ec <> 0) then
|
|
DayCount := 0;
|
|
FDateUpdated := ExchBaseDate + DayCount;
|
|
|
|
Str := List.Values['rate'];
|
|
if Str = '' then
|
|
FRate.SetToOne
|
|
else begin
|
|
{the INI file stores rates with a decimal *point*; if the locale
|
|
uses something else (eg, a comma) we'll need to switch it for
|
|
the AsString property, which obeys the locale}
|
|
if ({$IFDEF DELPHIXE2}FormatSettings.{$ENDIF}DecimalSeparator <> '.') then
|
|
ReplaceCh(Str, '.', {$IFDEF DELPHIXE2}FormatSettings.{$ENDIF}DecimalSeparator);
|
|
FRate.AsString := Str;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TStExchangeRate.SameSourceAndTarget(
|
|
aRate: TStExchangeRate): Boolean;
|
|
{
|
|
Tests whether the specified rate has the same source and target currencies.
|
|
Returns True of the Source and Target currencies are the same, False otherwise
|
|
}
|
|
begin
|
|
Result := False;
|
|
if Assigned(aRate) then
|
|
Result := (AnsiCompareText(Source, aRate.Source) = 0) and
|
|
(AnsiCompareText(Target, aRate.Target) = 0);
|
|
end;
|
|
|
|
procedure TStExchangeRate.SaveToList(List: TStrings);
|
|
{ create persistent representation of item }
|
|
var
|
|
Str : String;
|
|
DayCount : integer;
|
|
begin
|
|
if Assigned(List) then begin
|
|
List.Clear;
|
|
List.Add('source=' + FSource);
|
|
List.Add('target=' + FTarget);
|
|
List.Add('intermediate=' + FIntermediate);
|
|
Str := FRate.AsString;
|
|
if ({$IFDEF DELPHIXE2}FormatSettings.{$ENDIF}DecimalSeparator <> '.') then
|
|
ReplaceCh(Str, {$IFDEF DELPHIXE2}FormatSettings.{$ENDIF}DecimalSeparator, '.');
|
|
List.Add('rate=' + Str);
|
|
List.Add('type=' + MakeXChgStr(FConversionType));
|
|
DayCount := trunc(FDateUpdated - ExchBaseDate);
|
|
if DayCount < 0 then
|
|
DayCount := 0;
|
|
List.Add('date=' + IntToStr(DayCount));
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRate.SetRate(const Value: TStDecimal);
|
|
begin
|
|
FRate.Assign(Value);
|
|
end;
|
|
|
|
procedure TStExchangeRate.Update;
|
|
{ fire update event }
|
|
var
|
|
NewDate : TDateTime;
|
|
begin
|
|
if Assigned(FOnGetRateUpdate) then begin
|
|
NewDate := DateUpdated;
|
|
FOnGetRateUpdate(Self, Rate, NewDate);
|
|
DateUpdated := NewDate;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TStExchangeRateList }
|
|
constructor TStExchangeRateList.Create;
|
|
begin
|
|
inherited Create;
|
|
FRates := TStringList.Create;
|
|
FRates.Sorted := True;
|
|
FRates.Duplicates := dupIgnore;
|
|
end;
|
|
|
|
destructor TStExchangeRateList.Destroy;
|
|
begin
|
|
Clear;
|
|
FRates.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.Add(ARate: TStExchangeRate);
|
|
{
|
|
Adds the given exchange rate to the list
|
|
|
|
Since FRates list is set for dupIgnore, if Rate already exists, the
|
|
new values will be discarded
|
|
|
|
To modify an existing rate, use the Rates property or the UpdateRate
|
|
method, or delete the existing Rate and re-add it
|
|
}
|
|
begin
|
|
if Assigned(ARate) then
|
|
FRates.AddObject(MakeEntry(ARate.Source, ARate.Target), ARate);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.AddByValues(const Source, Target,
|
|
Intermediate : String; Rate : Double; ConversionType : TStConversionType;
|
|
DateUpdated : TDateTime);
|
|
{
|
|
Create new rate with provided characteristics and add it to the list
|
|
|
|
Since FRates list is set for dupIgnore, if Rate already exists, the
|
|
new values will be discarded
|
|
|
|
To modify an existing rate, use the Rates property or the UpdateRate
|
|
method, or delete the existing Rate and re-add it
|
|
}
|
|
var
|
|
TempRate : TStExchangeRate;
|
|
begin
|
|
TempRate := TStExchangeRate.Create;
|
|
TempRate.Source := Source;
|
|
TempRate.Target := Target;
|
|
TempRate.Intermediate := Intermediate;
|
|
TempRate.ConversionType := ConversionType;
|
|
TempRate.DateUpdated := DateUpdated;
|
|
TempRate.Rate.AssignFromFloat(Rate);
|
|
Add(TempRate);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.Assign(AList: TStExchangeRateList);
|
|
var
|
|
i : Integer;
|
|
begin
|
|
if Assigned(AList) then begin
|
|
{ if Rate Lists already point to same list then don't do anything }
|
|
if FRates = AList.FRates then Exit;
|
|
|
|
{ empty list }
|
|
Clear;
|
|
|
|
{ add items from new list }
|
|
for i := 0 to Pred(AList.Count) do
|
|
Add(AList.Items[i]);
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.Clear;
|
|
{ Clears all of the exchange rates from this table. }
|
|
var
|
|
i : Integer;
|
|
begin
|
|
for i := Pred(FRates.Count) downto 0 do begin
|
|
DeleteRate(i);
|
|
end;
|
|
end;
|
|
|
|
function TStExchangeRateList.Contains(
|
|
ARate: TStExchangeRate): Boolean;
|
|
{
|
|
Returns true if an exchange rate already exists with this rate's source,
|
|
target pair.
|
|
}
|
|
begin
|
|
Result := False;
|
|
if Assigned(ARate) then
|
|
Result := ContainsByName(ARate.Source, ARate.Target);
|
|
end;
|
|
|
|
function TStExchangeRateList.ContainsByName(const Source,
|
|
Target: String): Boolean;
|
|
{
|
|
Returns true if an exchange rate already exists with this one's
|
|
source and target ISOName Strings
|
|
}
|
|
begin
|
|
Result := FRates.IndexOf(MakeEntry(Source, Target)) >= 0;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.Convert(const Source, Target: String;
|
|
Amount, Result: TStDecimal);
|
|
{
|
|
convert Amount from Source currency to Target currency,
|
|
return new value in Result
|
|
}
|
|
begin
|
|
{Amount and Result must be created}
|
|
if (Amount = nil) or (Result = nil) then
|
|
raise EStException.CreateResTP(stscMoneyInvalidExchangeParams, 0);
|
|
|
|
{set the result value equal to the amount being converted}
|
|
Result.Assign(Amount);
|
|
|
|
{convert, allowing triangular exchanges}
|
|
ConvertPrim(Source, Target, Result, true);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.ConvertPrim(const aSource, aTarget : string;
|
|
aAmount : TStDecimal;
|
|
aAllowTriangular : boolean);
|
|
var
|
|
Rate : TStExchangeRate;
|
|
begin
|
|
{ do we have an entry for a Source->Target conversion? }
|
|
if not ContainsByName(aSource, aTarget) then
|
|
raise EStException.CreateResFmtTP(stscMoneyNoSuchExchange,
|
|
[aSource, aTarget], 0);
|
|
|
|
{get the exchange rate}
|
|
Rate := Rates[aSource, aTarget];
|
|
|
|
{for a simple multiply or divide conversion, the Rate object can
|
|
handle that by itself}
|
|
if (Rate.ConversionType = ctMultiply) or
|
|
(Rate.ConversionType = ctDivide) then begin
|
|
Rate.Convert(aAmount, aAmount);
|
|
Exit;
|
|
end;
|
|
|
|
{if a triangular exchange is not allowed, raise an error}
|
|
if not aAllowTriangular then
|
|
raise EStException.CreateResTP(stscMoneyTriExchUsesTriExch, 0);
|
|
|
|
{if the exchange rate is not triangular, raise an error}
|
|
if (Rate.ConversionType <> ctTriangular) then
|
|
raise EStException.CreateResTP(stscMoneyInvalidExchangeParams, 0);
|
|
|
|
{the conversion is triangular: check the intermediate currency}
|
|
if (Rate.Intermediate = '') then
|
|
raise EStException.CreateResTP(stscMoneyInvalidExchangeParams, 0);
|
|
|
|
{check to see if we have the two exchange rates}
|
|
if (not ContainsByName(aSource, Rate.Intermediate)) or
|
|
(not ContainsByName(Rate.Intermediate, aTarget)) then
|
|
raise EStException.CreateResFmtTP(stscMoneyMissingIntermediateRate,
|
|
[aSource, aTarget], 0);
|
|
|
|
{convert the amount from the Source to the Intermediate currency,
|
|
and then the result from the Intermediate to the Target currency;
|
|
triangular exchanges are *not* allowed to avoid infinite loops}
|
|
ConvertPrim(aSource, Rate.Intermediate, aAmount, false);
|
|
ConvertPrim(Rate.Intermediate, aTarget, aAmount, false);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.Delete(ARate: TStExchangeRate);
|
|
{
|
|
delete specified rate from list
|
|
fails silently if no matching rate exists in list
|
|
}
|
|
begin
|
|
DeleteByName(ARate.Source, ARate.Target);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.DeleteByName(const Source,
|
|
Target: String);
|
|
{
|
|
delete rate from list as determined by Source and Target
|
|
fails silently if no matching rate exists in list
|
|
}
|
|
var
|
|
Idx : Integer;
|
|
begin
|
|
{ find item in list }
|
|
Idx := FRates.IndexOf(MakeEntry(Source, Target));
|
|
|
|
{ if it exists, remove it }
|
|
if Idx >= 0 then
|
|
DeleteRate(Idx);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.DeleteRate(Index : Integer);
|
|
{ remove Rate from list by index }
|
|
{ no error checking that Index is in Range, should be done by caller }
|
|
begin
|
|
(FRates.Objects[Index] as TStExchangeRate).Free;
|
|
FRates.Delete(Index);
|
|
end;
|
|
|
|
function TStExchangeRateList.GetCount: Integer;
|
|
begin
|
|
Result := FRates.Count;
|
|
end;
|
|
|
|
function TStExchangeRateList.GetItem(Index: Integer): TStExchangeRate;
|
|
{ return Exchange rate by index }
|
|
begin
|
|
if not ((0 <= Index) and (Index < FRates.Count)) then
|
|
raise EStException.CreateResFmtTP(stscBadIndex, [IntToStr(Index)], 0);
|
|
Result := (FRates.Objects[Index] as TStExchangeRate);
|
|
end;
|
|
|
|
function TStExchangeRateList.GetRate(const Source,
|
|
Target: String): TStExchangeRate;
|
|
{ return Exchange rate by Source and Target }
|
|
var
|
|
Idx : Integer;
|
|
begin
|
|
Idx := FRates.IndexOf(MakeEntry(Source, Target));
|
|
if Idx >= 0 then begin
|
|
Result := (FRates.Objects[Idx] as TStExchangeRate);
|
|
end
|
|
else
|
|
raise EStException.CreateResFmtTP(stscMoneyNoSuchExchange, [Source, Target], 0);
|
|
end;
|
|
|
|
procedure TStExchangeRateList.LoadFromFile(const AFileName: TFileName);
|
|
var
|
|
FS : TFileStream;
|
|
begin
|
|
FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(FS);
|
|
finally
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.LoadFromStream(AStream: TStream);
|
|
{ build Rate list from stream of Rate data }
|
|
var
|
|
i : Integer;
|
|
IniStrm : TStIniStream;
|
|
Entries, Sections : TStringList;
|
|
CurRate : TStExchangeRate;
|
|
begin
|
|
IniStrm := nil;
|
|
Entries := nil;
|
|
Sections := nil;
|
|
CurRate := nil;
|
|
try
|
|
IniStrm := TStIniStream.Create(AStream);
|
|
Entries := TStringList.Create;
|
|
Sections := TStringList.Create;
|
|
{ create "index" of sections }
|
|
IniStrm.ReadSections(Sections);
|
|
|
|
{ iterate sections }
|
|
for i := 0 to Pred(Sections.Count) do begin
|
|
{ get settings as a list of <Name>=<Value> pairs }
|
|
IniStrm.ReadSectionValues(Sections[i], Entries);
|
|
|
|
{ build new rate item from settings }
|
|
CurRate := TStExchangeRate.Create;
|
|
CurRate.LoadFromList(Entries);
|
|
|
|
{ add to list }
|
|
Add(CurRate);
|
|
CurRate := nil;
|
|
end;
|
|
finally
|
|
Sections.Free;
|
|
Entries.Free;
|
|
IniStrm.Free;
|
|
CurRate.Free;
|
|
end;
|
|
end;
|
|
|
|
function TStExchangeRateList.MakeEntry(const Source, Target : String) : String;
|
|
{ format conversion entry header from Source and Target }
|
|
begin
|
|
Result := Source + ':' + Target;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.SaveToFile(const AFileName: TFileName);
|
|
var
|
|
FS : TFileStream;
|
|
begin
|
|
if not FileExists(AFileName) then begin
|
|
FS := TFileStream.Create(AFileName, fmCreate);
|
|
FS.Free;
|
|
end;
|
|
|
|
FS := TFileStream.Create(AFileName, fmOpenWrite or fmShareDenyNone);
|
|
try
|
|
SaveToStream(FS);
|
|
finally
|
|
FS.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.SaveToStream(AStream: TStream);
|
|
{ persist list of Rate data to a stream }
|
|
var
|
|
i : Integer;
|
|
IniStrm : TStIniStream;
|
|
Entries : TStringList;
|
|
CurRate : TStExchangeRate;
|
|
begin
|
|
IniStrm := nil;
|
|
Entries := nil;
|
|
try
|
|
IniStrm := TStIniStream.Create(AStream);
|
|
Entries := TStringList.Create;
|
|
{ for each maintained Rate item }
|
|
for i := 0 to Pred(FRates.Count) do begin
|
|
|
|
{ get reference to the Rate }
|
|
CurRate := (FRates.Objects[i] as TStExchangeRate);
|
|
|
|
{ make entries for Rate }
|
|
CurRate.SaveToList(Entries);
|
|
|
|
{ write entries as a new section to INI stream }
|
|
IniStrm.WriteSection(MakeEntry(CurRate.Source, CurRate.Target),
|
|
Entries);
|
|
end;
|
|
finally
|
|
Entries.Free;
|
|
IniStrm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TStExchangeRateList.UpdateRate(const Source,
|
|
Target: String; Rate: TStDecimal);
|
|
{
|
|
Modifies the exchange rate specified by the source and target
|
|
assumes rate already exists, use Add or AddByValues to add new rates
|
|
}
|
|
var
|
|
Idx : Integer;
|
|
begin
|
|
if not Assigned(Rate) then
|
|
raise EStException.CreateResTP(stscMoneyNilParameter, 0);
|
|
|
|
Idx := FRates.IndexOf(MakeEntry(Source, Target));
|
|
if Idx >= 0 then begin { conversion already exists for source and target }
|
|
{ update Rate to reflect new rate }
|
|
(FRates.Objects[Idx] as TStExchangeRate).Rate.Assign(Rate);
|
|
end
|
|
{ else no such rate }
|
|
end;
|
|
|
|
initialization
|
|
ExchBaseDate := EncodeDate(1980, 1, 1);
|
|
end.
|
|
|