Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
| Author |
Topic |
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-05 : 14:18:38
|
| So I am working on a reservation management system. I have hit a snag in designing the way it tracks transactions. I want to keep it simple. I don't need any fancy POS style transaction functionality.How have you guys handled tracking multiple forms of payments and the information that goes along with it?For example, I have transactions and transaction types tables. When someone pays with a credit card I have two fields in the transaction table, 'ccNum' and 'ccExp' to track that information. But whens someone pays with a check, I need a field to track a check number, so I have a 'checkNum' field. So then I figured, why have the transaction types table? If there is a number in the 'ccNum' field I can tell if it is a credit transaction, and the same for checks, and if both are NULL I can tell if it is a cash transaction. Any comments would be appreciated.- Eric |
|
|
Michael Valentine Jones
Yak DBA Kernel (pronounced Colonel)
7020 Posts |
Posted - 2005-07-05 : 15:57:52
|
| I think you should retain the transaction types table, because otherwise you will have to use that logic in each query. Plus later on you may have to handle some other kind of paymnets and the situation will get even more complicated. For example, newspapaer coupons, manufacturers coupons, store credits, wire transfers, etc.CODO ERGO SUM |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-05 : 16:17:49
|
| Yeah, we discussed that. After some thought, I am leaning towards keeping the transactions and transaction type tables. And then just having "numbers" and "exp" fields that will store the cc number and experiation date if needed, or just a check number with the exp field null, or both fields would just be blank if its a cash transaction.- Eric |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-05 : 16:18:46
|
| I'm just curious what normalization rule is being violated then?- Eric |
 |
|
|
Michael Valentine Jones
Yak DBA Kernel (pronounced Colonel)
7020 Posts |
Posted - 2005-07-05 : 16:32:29
|
| I would keep the individual columns, because it make it easier to apply rules for allowable data.You could have tables specific for handeling each type of payment transaction. If the data is different enough for each one. that may be a better option. You might have a payment line item, with generic information, like date and amount, and then tables that are related to it by (0 or 1) to 1 relationships for specific types of transactions.CODO ERGO SUM |
 |
|
|
rockmoose
SQL Natt Alfen
3279 Posts |
Posted - 2005-07-05 : 18:32:58
|
quote: Originally posted by stephe40 I'm just curious what normalization rule is being violated then?- Eric
The way You have described things, I don't think any normal form is being violated.All attributes in the transaction table would be dependent on the key or a superkey of the table:{Key} -> {TransactionType,Date,Amount,,,} <-- these attributes are dependent on the Key{Key,TransactionType} -> {ccNum,ccExp,checkNum} <-- these attributes are dependent on a superkeySo my guess is that You are at least in BCNF.Post the table structure if You want to.rockmoose |
 |
|
|
byrmol
Shed Building SQL Farmer
1591 Posts |
Posted - 2005-07-05 : 18:49:45
|
| >>I'm just curious what normalization rule is being violated then?Without the TransactionType table, almost certainly 3NF and 2NF>>{Key,TransactionType} -> {ccNum,ccExp,checkNum} <-- these attributes are dependent on a superkeyI reckon that breaks 3NF (Mutual independance seems to be violated)DavidMA front-end is something that tries to violate a back-end. |
 |
|
|
rockmoose
SQL Natt Alfen
3279 Posts |
Posted - 2005-07-06 : 04:14:49
|
Oh dear, I'm so confused Yes, I can relate to the mutual independence breach.But then again we don't have transitive dependency:.....Key -> TransactionType.....TransactionType -> {ccNum,ccExp,checkNum}Or do we ?We do have FD on the superkey.....(Key,TransactionType) -> {ccNum,ccExp,checkNum}Is that transitive ?Given the relations, which projections can be taken, to properly normalize the schema ?I have tried below, but there are probably other (better?) ways.(maybe there is a FD ccNum -> ccExp as well, but we can overlook that)Schemas with FD's written out--------------------------------------------------------------SCHEMA1 (Which normal forms does it break and Why?)--------------------------------------------------------------R0(TransactionType) PK(TransactionType)R(Key,TransactionType,Date,Amount,ccNum,ccExp,checkNum) PRIMARY KEY(Key) FOREIGN KEY(TransactionType) REFERENCES R0 Key -> TransactionType Key -> Date Key -> Amount {Key,TransactionType} -> ccNum {Key,TransactionType} -> ccExp {Key,TransactionType} -> checkNum--------------------------------------------------------------SCHEMA2 (R is projected into R1+R2+R3) (Is this 3NF?, are there other better ways?)--------------------------------------------------------------R0(TransactionType) PK(TransactionType)R1(Key,Date,Amount) PRIMARY KEY(Key) Key -> Date Key -> Amount R2(Key,TransactionType,ccNum,ccExp) PRIMARY KEY(Key) FOREIGN KEY(Key) REFERENCES R1 FOREIGN KEY(TransactionType) REFERENCES R0 CHECK(TransactionType = 'CreditCard') Key -> TransactionType Key -> ccNum Key -> ccExpR3(Key,TransactionType,checkNum) PRIMARY KEY(Key) FOREIGN KEY(Key) REFERENCES R1 FOREIGN KEY(TransactionType) REFERENCES R0 CHECK(TransactionType = 'Check') Key -> TransactionType Key -> checkNumDEFINITIONS OF 3NF, different sources.quote: "http://en.wikipedia.org/wiki/Database_normalization"Third normal formThird normal form (or 3NF) requires that there are no non-trivial functional dependencies of non-key attributes on something other than a superset of a candidate key.A relation is in 3NF if none of the non-Primary Key attributes are a fact about any other non-Primary Key attribute.Another way of saying this is that all non-key attributes are mutually independent (i.e. there should not be transitive dependencies).
quote: "Introduction to Databse Systems" 6th ed. C.J. DateThird Normal Form (even more formal definition): A relation is in third normalform(3NF) if and only of, for all time, each tuple consists of a primary key valuethat identifies some entity, together with a set of zero or more mutually indepen-dent attribute values that describe that entity in some way.Third Normal Form (definition assuming only one candidate key, which is thusthe primary key): A relation is in 3NF if and only if it is in 2NF and every nonkeyattribute is nontransitively dependent on the primary key. ("No transitive depend-encies" implies no mutual dependencies, in the sense of that term explained at thebeginning of this section.)- Two or more attributes are mutually independent if none of them is functionallydependent on any combination of the others. Such independence implies that eachsuch attribute can be updated independently of all the rest.
quote: "Fundamentals of Database Systems" 4th ed. Elmasri,NavatheDefinition. A relation schema R is in third normal form (3NF) if, whenever anontrivial functional dependency X->A holds in R, either(a) X is a superkey of R, or(b) A is a prime attribute of R.
rockmoose |
 |
|
|
byrmol
Shed Building SQL Farmer
1591 Posts |
Posted - 2005-07-06 : 05:28:10
|
It's the TransactionType attribute that's screwing it up.(SCHEMA 1)The mutual dependance is the fact the if the non-key attribute (TransactionType) is updated, then the other 3 non-key attributes need to be updated as well if the predicate is to be satisfied.If you drop that attribute all together (SCHEMA 2) we are left with the relation istelf defining the transaction type. Transaction (Key, Date, Amount)CreditCardTransaction(Key, ccNum,ccExp)ChequeTransaction(Key, checkNum) In 1 to 0 | 1 FK's...Just like Michael suggested.It is these types of modelling scenario's where views come into there own for both data integrity and middle-tier intergration.Physically, we main gain some SELECT advantage with the TransactionType column tacked on to all the tables but that's the posters call..DavidMA front-end is something that tries to violate a back-end. |
 |
|
|
rockmoose
SQL Natt Alfen
3279 Posts |
Posted - 2005-07-06 : 06:48:00
|
The "superset of a candidate key" definition got me.And that definition still somewhat confuses me.And, yes, the TransactionType relation (+ attribute) is redundant in SCHEMA 2,because the relations define the type.So if we have this relation (no TransactionType table as suggested by Eric(poster) once):Transaction(Key,Date,Amount,ccNum,ccExp,checkNum).....primary key(Key).....check(either {ccNum,ccExp} or checkNum filled in)Did You say that it violates 2NF ?, How so?ccNum,ccExp,checkNum are not mutually independent, so 3NF.Sorry for being such a dumbass today...rockmoose |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-06 : 09:56:33
|
quote: Originally posted by rockmoose.....check(either {ccNum,ccExp} or checkNum filled in)
Don't forget that if all three are empty, then it would be a cash transaction.- Eric |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-06 : 10:18:38
|
Here is them part of my schema that we are discussing... FWIWCREATE TABLE [dbo].[guests] ( [guestID] [int] IDENTITY (1000, 1) NOT NULL , [firstName] [varchar] (100) , [lastName] [varchar] (100) , [middleName] [varchar] (50) , [address1] [varchar] (200) , [address2] [varchar] (200) (most columns removed to shorten this up)) ON [PRIMARY]GOCREATE TABLE [dbo].[reservations] ( [resID] [int] IDENTITY (1, 1) NOT NULL , [notes] [varchar] (5000) , [guestID] [int] NOT NULL , [yearID] [int] NOT NULL , [pending] [bit] NOT NULL , [dateAdded] [datetime] NULL ) ON [PRIMARY]GOCREATE TABLE [dbo].[transactions] ( [transID] [int] IDENTITY (1, 1) NOT NULL , [payTypeID] [int] NOT NULL , [resID] [int] NOT NULL , [amount] [numeric](18, 2) NOT NULL , [numbers] [varchar] (50) , [ccExp] [char] (4) , [transDate] [datetime] NOT NULL ) ON [PRIMARY]GOCREATE TABLE [dbo].[paymentTypes] ( [payTypeID] [int] IDENTITY (1, 1) NOT NULL , [payTypeDesc] [varchar] (50) ) ON [PRIMARY]GO-- PKs....... --ALTER TABLE [dbo].[guests] ADD CONSTRAINT [PK_guests] PRIMARY KEY CLUSTERED ( [guestID] ) WITH FILLFACTOR = 90 ON [PRIMARY] GOALTER TABLE [dbo].[reservations] ADD CONSTRAINT [PK_reservations] PRIMARY KEY CLUSTERED ( [resID] ) WITH FILLFACTOR = 90 ON [PRIMARY] GOALTER TABLE [dbo].[transactions] ADD CONSTRAINT [PK_transactions] PRIMARY KEY CLUSTERED ( [transID] ) WITH FILLFACTOR = 90 ON [PRIMARY] GOALTER TABLE [dbo].[paymentTypes] ADD CONSTRAINT [PK_paymentTypes] PRIMARY KEY CLUSTERED ( [payTypeID] ) WITH FILLFACTOR = 90 ON [PRIMARY] GO-- Check Constraints .......... --ALTER TABLE [dbo].[reservations] ADD CONSTRAINT [FK_reservations_guests] FOREIGN KEY ( [guestID] ) REFERENCES [dbo].[guests] ( [guestID] ) ON DELETE CASCADE ON UPDATE CASCADE GOALTER TABLE [dbo].[transactions] ADD CONSTRAINT [FK_transactions_paymentTypes] FOREIGN KEY ( [payTypeID] ) REFERENCES [dbo].[paymentTypes] ( [payTypeID] ) ON DELETE CASCADE ON UPDATE CASCADE , CONSTRAINT [FK_transactions_reservations] FOREIGN KEY ( [resID] ) REFERENCES [dbo].[reservations] ( [resID] ) ON DELETE CASCADE ON UPDATE CASCADE GO As you can see, at this point, I have a payment types table, and a transactions table which has a FK from the payment types table. I also have the fields "numbers" "ccExp." I plan to store both credit card numbers and check numbers in the "numbers" field. The "ccExp" field will have a value it in when its a credit transaction, and be blank when its a check transaction. Both fields, "numbers" and "ccExp" will be empty when its a cash transaction.My logic for storing both cc numbers and check numbers in the "numbers" field goes like this. If I had seperate fields for each, one for cc numbers and one for check numbers, and then a new payment type is added, lets say we now accept paypal, there would be no logical place to store a paypal confirmation number. With the field just named "numbers" that number could go there and no database modification would be nessary. Again I am not 100% happy with this design. But I think its probably the simplest to comprehend. Which may pay off in the long run when I die and someone else has to take over.- Eric |
 |
|
|
Michael Valentine Jones
Yak DBA Kernel (pronounced Colonel)
7020 Posts |
Posted - 2005-07-06 : 11:49:41
|
| I don't like the name NUMBERS for the column bacause it is too vague. It is really something like PAYMENT_SOURCE_ACCOUNT_NUMBER. NUMBERS is also misleading because it is actually a varchar(50) column.I dislike overloading a column with multiple types of data. My experience is that I always find a reason to regret it later. You will probably find that each type of trasaction requires its own set of data, for example, bank routing number and account number for a check. I think it is better to plan that you will add columns for new payment types as needed, or add new tables to handle the specific info for each payment type.CODO ERGO SUM |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-06 : 13:00:25
|
| Yes I agree that "numbers" is a little vague. But the point here is that there should not be any database modifications required if a new payment type is added. Thats the whole point of having the seperate payment types table. Option 1: have a seperate field in the transaction table for each payment type. And whenever a new payment type is needed a different field is added to track whatever info it needs. As some point there would be a buch of field that are null for each row.Option 2: Have a seperate table for each payment type. The transaction table would then have multiple child tables. So to get a report of all transactions for the current day there would need to be miltiple joins to for a data set that is identical to option 1 for reporting. This would then also require a new table evertime a new payment type is added. Is the overhead of modifying the database, (either adding columns or adding tables), less than overloading one column?- Eric |
 |
|
|
byrmol
Shed Building SQL Farmer
1591 Posts |
Posted - 2005-07-06 : 20:24:03
|
| >>But the point here is that there should not be any database modifications required if a new >>payment type is added. Just use a big string field and stuff every thing into it...credit card numbers, expiry date, cheque, pay pal.. anything you want... I am sure the front end will love that.. :-)Will the front end have to change when a new payment type is added? If it does then the maintenance side of the argument is mute..>>So to get a report of all transactions for the current day there would need to be miltiple >>joins to for a data setHow many times a day does this report get run? Once? This is the last thing you should be worried about at this stage.. Concentrate on the data and making it bulletproof to mistakes..I am yet to come across a client who willing sacrifices a "slow" truth for a "fast" lie.Whatever design you choose, I STRONGLY recommend using views for (at the very least) data integrity of the different payment types.DavidMA front-end is something that tries to violate a back-end. |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-07 : 11:03:56
|
quote: Originally posted by byrmol Just use a big string field and stuff every thing into it...credit card numbers, expiry date, cheque, pay pal.. anything you want... I am sure the front end will love that.. :-)Will the front end have to change when a new payment type is added? If it does then the maintenance side of the argument is mute..How many times a day does this report get run? Once? This is the last thing you should be worried about at this stage.. Concentrate on the data and making it bulletproof to mistakes..I am yet to come across a client who willing sacrifices a "slow" truth for a "fast" lie.Whatever design you choose, I STRONGLY recommend using views for (at the very least) data integrity of the different payment types.DavidMA front-end is something that tries to violate a back-end.
I understand your point. The front end is yet to be designed, we have a few preliminary mockups but nothing is created for real yet. I think the interface could be easily designed to not require an update if a payment type is added. That’s why the field was just called ‘numbers’ so that when someone adds a payment type, there is a field for whatever number they want. But I do see your point. Because what if a new payment type requires two numbers. Then in that case we would need to add a column and modify the interface. Double whammy.This is why I said that I am not 100% happy with the current setup. I am defiantly not opposed to changing it to a better setup if one comes along.In regard to the report I mentioned. You were correct, the report would only be run once a day, maybe twice. But is not a real valid point in this debate.At this point I don’t think I have seen or thought of any “bulletproof” solution. I’ll keep thinking.- Eric |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-07 : 11:08:21
|
Here is another option that I came up with but it is not 100% bulletproof.TablespaymentTypes (payTypeID int, payTypeDescrip varchar(50))transactions (transID int, payTypeID int, transDate, datetime, amount money)extendedPaymentProperties (payPropID int, payPropName varchar(50))extendedPaymentPropertyValues (transID int, payPropID int, value varchar(50)) Example Data extendedPaymentProperties payPropID payPropName1 Credit Card Number2 Credit Card Exp3 Check Number4 PayPal Confirmation Number5 Some other useless numberpaymentTypespayTypeID payTypeDesc1 Credit2 Check3 Cash4 PaypaltransactionstransID payTypeID transDate amount1001 1 1/1/05 1:00 AM 50.341002 4 1/1/05 1:11 AM 123.131003 2 1/1/05 1:34 AM 200.001004 3 1/1/05 1:35 AM 123.34 Then extendedPaymentPropertyValues would look like this.transID payPropID value1001 1 12341234123412341001 2 06/081002 4 123AUD28D902KL1003 3 4321 The problem with this design is the datatype of “value” in the extendedPaymentPropertyValues table. What if for some reason there needs to be another date and time stored. That date value then would need to be stored as a character. What do you guys think about this setup?I am very interested in your opinions because I use this same type of design in another part of the database. We needed a way of storing start and stop times for certain properties, like costs and stuff. Mostly because room rates change as time goes on.Anyway, TIA- Eric |
 |
|
|
byrmol
Shed Building SQL Farmer
1591 Posts |
Posted - 2005-07-07 : 18:50:58
|
| Eric,The final design is referred to as a EVA (Enitity, Value, Attribute) design and is very hard to maintain well, due primarily to poor type control. Don't go there...You have got to keep remembering the difference between your logical design and the physcially implemention of it. Don't even think about SQL Server when designing your logical model.Ideally, PaymentType would form a single attribute with the various paymenttypes being inherited from a basetype.eg.Transaction (TranID INT, DateOf DATE, Amount MONEY, TransactionData TransactionType)abstract TransactionType (TypeName VARCHAR(10) = 'Base')CashType: TransactionType (override TypeName ='Cash')CreditType: TransactionType(override TypeName ='Credit', Credit# INT, Expiry DATE)etc...You still have to alter the DBMS and UI when new types arrive but I reckon it could be done it about 10 minutes flat..So... considering type support is limited in our physcially DBMS, we have to innovate. Keep remembering the logical model hasn't changed, only how the hell we are going to stuff into SQL Server...Hence the idea of using tables as the different types of transactions... Wrap a few views around the whole thing, and you should come very close to your logical model..A simple factory pattern in the middle tier handles these situations very well in my experience and makes modifications almost trivial....DavidMA front-end is something that tries to violate a back-end. |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-08 : 10:41:54
|
| Even after all this I still don't know what to do. I'm having trouble actually visualizing how David's suggestion would be layed out. I guess Ill keep debating this in my head and try to come up with something.- Eric |
 |
|
|
stephe40
Posting Yak Master
218 Posts |
Posted - 2005-07-11 : 14:39:28
|
| So going with Davids suggestion the tables would look like...paymentTypes (payTypeID PK, payTypeDesc)transactions (transID PK, payTypeID FK, amount, transDate)creditCards(ccID PK, ccNumber, ccExp, transID FK)checks (checkID PK, checkNum, transID FK)Am I understanding it right?- Eric |
 |
|
|
byrmol
Shed Building SQL Farmer
1591 Posts |
Posted - 2005-07-11 : 20:32:35
|
>>Am I understanding it right?Partially...The transID becomes the PK and FK in the creditCards and checks table.Please tell me that payTypeID is NOT planning on being an identity column.. I could rant on for hours about blindly showing identity column anywhere...I am working on similar designs at the moment so I will give a working example of the pattern I use for this... First up the physcial schema...create schema authorization dbocreate table paymentTypes (payTypeID varchar(15) primary key , payTypeDesc varchar(255) not null default(''))create table transactionBase (transID INT IDENTITY(1,1) primary key check(transid > 0) , payTypeID varchar(15) not null, amount money not null check(amount > 0) , transDate datetime not null default(getdate()) , foreign key (payTypeID) references paymentTypes (payTypeID))create table transactionCreditCard(transID INT primary key default(IDENT_CURRENT('transactionBase')) , ccNumber varchar(15)not null check(len(ccNumber) > 0) , ccExp datetime not null , foreign key (transID) references transactionBase (transID))create table transactionCheck (transID INT primary key default(IDENT_CURRENT('transactionBase')) , checkNum varchar(35) not null , foreign key (transID) references transactionBase (transID))The only "tricky" part of that schema is the really cool default for the transID in the "sub type" tables.. We'll use this later on...Let's try and make it bullet proof...The WITH CHECK OPTION enforces any constraints defined in the select statement of the view...create view transaction_CashASselect transID, payTypeID, amount, transDatefrom dbo.transactionbaseWHERE payTypeID = 'Cash'with check optioncreate view transaction_Cheque_CONSTRAINTASselect tb.transID, tb.payTypeID, tb.amount, tb.transDatefrom dbo.transactionbase tbWHERE payTypeID = 'Cheque'with check optioncreate view transaction_CreditCard_CONSTRAINTASselect tb.transID, tb.payTypeID, tb.amount, tb.transDatefrom dbo.transactionbase tbWHERE payTypeID = 'Credit Card'with check optioncreate view transaction_ChequeASselect tb.transID, tb.payTypeID, tb.amount, tb.transDate, tc.checkNumfrom dbo.transactionbase tbinner join dbo.transactionCheck tc on tc.transID = tb.transIDWHERE payTypeID = 'Cheque'create view transaction_CreditCardASselect tb.transID, tb.payTypeID, tb.amount, tb.transDate, tc.ccNumber, tc.ccExpfrom dbo.transactionBase tbinner join dbo.transactionCreditCard tc on tc.transID = tb.transIDWHERE tb.payTypeID = 'Credit Card' The "Cash" transaction type only requires the one view (it is already updateable) but all "sub types" require 2 views and a INSTEAD OF trigger to be useful...create trigger trg_transaction_Cheque_INSERTon transaction_Chequeinstead of insertasinsert dbo.transaction_Cheque_CONSTRAINT ( payTypeID, amount, transDate) select paytypeid, amount, transdate from inserted iinsert dbo.transactionCheck (checkNum) select checkNum from inserted igocreate trigger trg_transaction_CreditCard_INSERTon transaction_CreditCardinstead of insertasinsert dbo.transaction_CreditCard_CONSTRAINT ( payTypeID, amount, transDate) select paytypeid, amount, transdate from inserted iinsert dbo.transactionCreditCard ( ccNumber, ccExp) select ccNumber, ccExp from inserted igo The only issue is that these triggers will fail on multi-row inserts... The default defined only copes with single row insertion. Notice that we insert into the base "_CONSTRAINT" view to ensure that the right type is enteredWe are left with 3 views that the front-end gang can play with all they like..insert paymentTypes (payTypeID, payTypeDesc) select 'Cash', '' union all select 'Cheque', '' union all select 'Credit Card', ''goinsert dbo.transaction_cash (payTypeID, amount, transDate) values ('Cash', RAND() * 1000, getdate())insert dbo.transaction_Cheque (transID, payTypeID, amount, transDate, checkNum) values (0, 'Cheque', RAND() * 1000, getdate(), 'd2345345345')insert dbo.transaction_CreditCard (transID, payTypeID, amount, transDate, ccNumber, ccExp) values (0, 'Credit Card', RAND() * 1000, getdate(), '123213', '01/05/2002')Finally you could create a "report" view..create view transaction_detailedasselect transID, payTypeID, amount, transDate, 'n/a' as detailsfrom dbo.transaction_Cashunion allselect transID, payTypeID, amount, transDate, checkNum from dbo.transaction_Chequeunion allselect transID, payTypeID, amount, transDate, ccNumber + '(' + CAST(ccExp as varchar(20)) + ')' as detailsfrom dbo.transaction_CreditCardgoselect * from transaction_cashselect * from transaction_chequeselect * from transaction_CreditCardselect * from transaction_detailedThe whole point is data integrity and 20 lines in the DBMS is worth hundreds of lines elsewhere...There is nothing stopping you using a single stored proc that contains IF logic to INSERT into the right view, thus giving the UI guys a single proc to insert into... I hope this helps...DavidMA front-end is something that tries to violate a back-end. |
 |
|
|
Next Page
|
|
|
|
|