sexta-feira, 20 de fevereiro de 2009

Análise de Requisitos

E aew pessoal,
Eu resolvi escrever esse artigo devido a uma visita em um suposto novo cliente, depois de ele ter solicitado a criação de um sistema.

Grande parte das vezes que visitamos um cliente que necessita de um software de gestão para a empresa, a primeira coisa que escutamos é: "Eu quero pouca coisa", "É coisa rápida e fácil".
Escutamos isso porquê o cliente tem pouca noção para análisar um problema, então é aí que entra o analista de sistemas para levantar os requisitos de sistema.


INTRODUÇÃO

A análise de requisitos é uma das sub-áreas da engenharia de software, que a função é detectar e documentar problemas do cenário atual para que eles sejam implementados com qualidade e com o mínimo de manutenção possível.

Creio que cerca de 80% dos erros nos projetos são causados por defeitos inseridos durante a análise e requisitos, causando um custo maior de manutencão e de testes do sistema e, o pior são os erros descobertos pelos usuários. Atire a primeira pedra o programador que nunca escutou: "Está dando problema!", "Esta com bug!", "Não funciona!". Além disso, a pior coisa para uma empresa é a perda de oportunidades e de confiança com os clientes.

Para terminar a introdução, segundo a wikipedia, Análise de requisitos é: " O estudo das características que o sistema deverá ter para atender às necessidades e expectativas do cliente.
Cada funcionalidade demandada pelo cliente deve ser analisada para verificar os possíveis impactos no desenvolvimento das demais funcionalidades do sistema, e verificado em conjunto com a equipe de desenvolvimento se as necessidades tecnológicas para a sua implementação estão disponíveis."


ESTUDO DE VIABILIDADE

Você esta prestes a criar a documentação necessária e a implementar o novo sistema. Mas será que é realmente viável a criação do mesmo?

Uma forma de avaliar a viabilidade de um projeto é obter, através de interação com as partes interessadas a resposta às seguintes questões:
  • Será que o sistema contribui para os objetivos da organização? (POR PARTE DO CLIENTE)
  • Dadas as restrições tecnológicas, organizacionais (econômicas, políticas, ambientais, recursos disponíveis) e temporais associadas ao projeto, será que o sistema pode ser implementado? (POR PARTE DOS ANALISTA/DESENVOLVEDOR)
  • Caso haja necessidade de integração entre diferentes sistemas, será que esta é possível? (POR PARTE DOS ANALISTA/DESENVOLVEDOR)
Creio que a primeira questão é a que mais "pega" em um estudo de viabilidade. Caso a resposta seja negativa, com certeza você terá dores de cabeça caso resolva tocar o projeto, pois o cliente nuncá ficará satisfeito mesmo se tudo estiver funcionando.


PROCESSOS DA ENGENHARIA DE REQUISITOS

Você deve estar se perguntando: "E quais são os passos para o levantamento de requisitos?"
  1. Identificação do problema (Questionar os problemas e as necessidades que o cliente esta enfrentando).
  2. Análise e negociação (Saber como funciona cada processo e a regra de negócio é fundamental!).
  3. Especificação e documentação (Documentar por meios legíveis todos os processos que cada tarefa terá. É nesta fase que se dá a produção do documento de especificação de requisitos).
  4. Validação (Rever os 3 passos anteriores e demonstrar que o documento de requisitos produzido corresponde ao sistema que o cliente pretende receber).

Esses passos podem ser feitos de vários meios: Entrevistas, Questionários, Workshops ou qualquer outro meio de obter o máximo de informações dos usuários.

Não podemos começar a implementar um sistema pelo que achamos que deve ser feito, por isso o usuário "trabalha", passando todas as informações necessárias.

Como criar a documentação jogando todos os requisitos funcionais e todos os processos que cada requisito terá, explicarei em outro Post...caso eu ainda esteja vivo com esse carnaval.

Abraços e até +.

quinta-feira, 12 de fevereiro de 2009

InputQuery / Inputbox com campo estilo senha

Hoje eu precisei pedir uma senha para o usuário do sistema, só que eu não queria criar um form só para solicitar a senha, dai eu resolvi usar InputQuery.
Só que havia um problema, o usuário não pode visualizar a senha enquanto digita. Então como fazer com que o InputQuery fique com o passwordChar igual a #?

O delphi não possui um componente/função que faça isso. Então eu fui ao bendito google e achei a função/procedimento que queria no Blog do Adriano Santos http://delphitodelphi.blogspot.com que além de escrever para o blog pessoal, cria alguns artigos para a revista ClubeDelphi da devMedia www.devmedia.com.br.

Para a função, você precisa adicionar na Uses, as seguintes unidades: Types, Controls e Windows.

Segue abaixo a função:
function InputQuerySenha(const ACaption, APrompt: string; var Value: string):
Boolean;
var
Form: TForm;
Prompt: TLabel;
Edit: TEdit;
DialogUnits: TPoint;
ButtonTop, ButtonWidth, ButtonHeight: Integer;
function GetAveCharSize(Canvas: TCanvas): TPoint;
var
I: Integer;
Buffer: array[0..51] of Char;
begin
for I := 0 to 25 do
Buffer[I] := Chr(I + Ord('A'));
for I := 0 to 25 do
Buffer[I + 26] := Chr(I + Ord('a'));
GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
Result.X := Result.X div 52;
end;
procedure Saida(Sender: TObject);
begin
if TEdit(Sender).Text = '' then
begin
ShowMessage('Vazio');
TEdit(Sender).SetFocus;
end;
end;
begin
Result := False;
Form := TForm.Create(Application);
with Form do
try
Canvas.Font := Font;
DialogUnits := GetAveCharSize(Canvas);
BorderStyle := bsDialog;
Caption := ACaption;
ClientWidth := MulDiv(180, DialogUnits.X, 4);
Position := poScreenCenter;
Prompt := TLabel.Create(Form);
with Prompt do
begin
Parent := Form;
Caption := APrompt;
Left := MulDiv(8, DialogUnits.X, 4);
Top := MulDiv(8, DialogUnits.Y, 8);
Constraints.MaxWidth := MulDiv(164, DialogUnits.X, 4);
WordWrap := True;
end;
Edit := TEdit.Create(Form);
with Edit do
begin
{Usando a fonte Wingdings e a letra "ele" em minúsculo, simula-se as "bolinhas" no lugar
de asterísco da senha}
Font.Name := 'Wingdings';
PasswordChar := 'l';
Parent := Form;
Left := Prompt.Left;
Top := Prompt.Top + Prompt.Height + 5;
Width := MulDiv(164, DialogUnits.X, 4);
MaxLength := 255;
Text := Value;
SelectAll;
end;
ButtonTop := Edit.Top + Edit.Height + 15;
ButtonWidth := MulDiv(50, DialogUnits.X, 4);
ButtonHeight := MulDiv(14, DialogUnits.Y, 8);
with TButton.Create(Form) do
begin
Parent := Form;
Caption := 'Confirmar';
ModalResult := mrOk;
Default := True;
SetBounds(MulDiv(38, DialogUnits.X, 4), ButtonTop, ButtonWidth,
ButtonHeight);
end;
with TButton.Create(Form) do
begin
Parent := Form;
Caption := 'Cancelar';
ModalResult := mrCancel;
Cancel := True;
SetBounds(MulDiv(92, DialogUnits.X, 4), Edit.Top + Edit.Height + 15,
ButtonWidth, ButtonHeight);
Form.ClientHeight := Top + Height + 13;
end;
if ShowModal = mrOk then
begin
Value := Edit.Text;
Result := True;
end;
finally
Form.Free;
end;
end;

Exemplo de uso:

procedure TForm1.Button1Click(Sender: TObject);
var
Texto : string;
begin
InputQuerySenha('Caption', 'Caption', Texto);
Edit1.Text := Texto;
end;

Abraços pessoal. T+

quinta-feira, 22 de janeiro de 2009

Threads - Conceito e exemplo em Delphi

Nesse post falarei a respeito de Threads no Delphi.
Threads são processos e segundo o dicionário, o verbo processar no termo computacional significa "tratar dados em computador a fim de obter informação sistematizada" ou "interpretar e executar instruções".
Toda vez que executamos um projeto no Delphi, o projeto aberto é uma thread na qual é o processo principal do seu sistema. Você pode estar pensando: "Mas para que eu preciso aprender sobre Threads?". Você já notou que toda vez que um trecho de código é executado no delphi, você só consegue mexer no seu sistema depois que tudo é executado. Não notou? Então faça o seguinte exemplo:

Crie um projeto VCL forms e arraste um button no seu formulário.
No Onclick de um botao, coloque o código abaixo, rode o programa e depois clique nele.
var
I: Integer;
begin
for I:=1 to 10000 do
begin

end;
end;

Notou que após que você clica no botao e enquanto o laço de repetição não foi finalizado, não é possível nem mover o seu formulário ou executar qualquer outra tarefa em seu aplicativo?
Isso acontece devido ao processo que esta rodando mas ainda não foi finalizado.
Mas e agora? Se eu quiser executar outra tarefa no meu aplicativo enquanto o laço de repetição ainda esta em loop?. É aqui que entra Threads de sistema!
Outro exemplo de Thread é o componente Timer do Delphi que enquanto você esta rodando seu aplicativo, você consegue fazer com que o timer atualize a hora em um label a cada segundo sem interferir nas outras execuções.


CRIANDO UMA CLASSE E TENDO COMO HERANÇA A CLASSE TTHREAD

Criaremos uma classe chamada TMinhaThread que extenderá a classe TThread que esta na unit Classes:

type
TMinhaThread = class(TThread)
private
procedure Execute; override;
end;

Na nossa classe criada, criamos uma procedure chamada Execute que deverá ser modificada(override/virtual) toda vez que herdamos a classe Tthread. Somos forçados a fazer isso pois a classe TThread possui o método Execute que é abstrato e segundo o conceito de Orienteção a objetos, toda classe que herda uma classe que possui métodos abstratos, deve implementar todos os métodos abstratos da mesma. Eu sei que você deve estar pensando: "E toda classe que possui métodos abstratos, deve ser abstrata.", mas orientação a objetos não é o foco do Post.

Pressione CTRL+SHIFT+C para o delphi criar a estrutura no método Execute.

procedure TMinhaThread.Execute;
var
I: Integer;
begin

try
for I:=0 to 10000 do
begin
Sleep(20);
end;
Except

end;
end;

Com a procedure já implementada, arraste outro button para o formulário e adicione o seguinte trecho de código:

var
//Cria a variavel minhaThread do tipo TMinhaThread
minhaThread: TMinhaThread;
begin
//Criação do objeto
minhaThread := TMinhaThread.Create(False);
minhaThread.FreeOnTerminate := true;
end;

O método create recebe um valor booleano que significa:
True - A Thread é iniciada porém ela é pausada ou seja, não executa o método Execute (imagine o componente Timer com a opção Enabled = false).
False - O método Execute é executado assim que a Thread é iniciada.

O método FreeOnTerminate, verifica se a Thread precisa ser liberada da memória assim que ela for finalizada. Com isso não precisamos dar um FreeAndNil.
Valor True para liberar da memória e False para permanecer armazenada.


Execute o botão e note que apesar do seu aplicativo estar em loop, você consegue movê-lo e até mesmo executar outras tarefas.

Outro exemplo onde podemos utilizar Threads é no download ou upload de múltiplos arquivos, pois com thread conseguimos fazer a transferência de vários arquivos ao mesmo tempo sem ser preciso esperarmos o término de cada um.


Todas as explicações que eu posto aqui no blog são de tarefas que pratico no dia-a-dia, mas caso algum leitor ache interessante eu postar algo de um determinado assunto, basta comentar em algum post ou me enviar um e-mail.

Blz pessoal?
See ya guys....

quarta-feira, 21 de janeiro de 2009

Votação de produtos com porcentagem e barra de progresso

E ai pessoal, tudo tranquilo?
Depois de mais ou menos 1 mes de ferias do blog, voltei aqui para postar mais uma implementação q fiz em um dos meus trabalhos.

No momento eu estou fazendo um site em PHP, onde há produtos e cada produto possui uma enquete com 5 opções:
1 - Péssimo produto
2 - Razoável
3 - Bom
4 - Muito bom
5 - Excelente

Eu não explicarei exatamente como fazer a enquete, ou seja, como incluir uma nova enquete ou como fazer com que a página receba os votos do usuário. Irei apenas enfatizar como pegar o total de votação e mostrar a porcentagem de cada opção e além disso, como mostrar o total da votação em uma barra de progresso horizontal.


TABELAS UTILIZADAS

Segue abaixo como estao as minhas tabelas na base de dados:

USUARIOS
-------------------
idusuario INT AUTOINCREMENT NOT NULL
nome VARCHAR(30) NOT NULL

PRODUTOS
-------------------
idproduto INT AUTOINCREMENT NOT NULL
nome VARCHAR(30) NOT NULL

VOTACOES_PRODUTOS
--------------------
idusuario INT
idproduto INT
data_votacao DATETIME
declaracao TINYINT (Campo que determina a opção do voto)

Bom, agora com nossas tabelas, irei mostrar a logica de como fazer a porcentagem de votos para cada opção.


PORCENTAGEM DE VOTOS POR OPÇÃO VOTADA


Para calcular a porcentagem de votos que uma opção teve, devemos fazer o seguinte calculo:
porcentagem é = quantidade total . (quantidade de votos/100)

Quantidade total = Número de registros da tabela votacoes_produtos.
Ex.: (SELECT COUNT(*) as qtde_total FROM votacoes_produtos)

Quantidade de votos = Número de votos que o produto obteve na determinada opção.
Ex.: (SELECT COUNT(*) as qtde_votos FROM votacoes_produtos WHERE idproduto = 3 and declaracao = 4)

ficaria assim em PHP:
$porcentagem = $qtde_total*($qtde_votos/100);
echo $porcentagem;

RECEBENDO A PORCENTAGEM DE TODAS AS OPÇÕES

Imagine a primeira opção (Péssimo produto) como 0% e a quinta opção (Excelente) como 100% da nossa barra de progresso.
Precisamos criar uma forma de preencher nossa barra de progresso pelas votações recebidas, tendo votos da primeira e última opção ou não.

Para fazer isso, faremos uma regra de três.
Vamos supor que 5 pessoas votaram, seus votos foram: 5, 3, 5, 1 e 2
A soma de todas as votações(opções), seria 16...correto?
O máximo de número de votos seria 5x5 = 25(100%)
Ai você faz aquela pergunta para obter a porcentagem: se para 25 pontos eu tenho 100%, quantos % eu obtive com 16 pontos?
Fácil!
25 = 100%
16 = x

x = (100*16)/25
x = 64%

Como diz o professor de inglês Arthur que aparece no youtube: IT'S EASY!!!


CRIANDO A BARRA DE PROGRESSO

Nossa barra de progresso será feita no esquema de Estrelas.
Faça download das seguintes imagens:




Para criar a barra de progresso, utilizaremos HTML puro.
Crie uma tabela com 100 pixels de largura, 0 de borda, cellpadding e cellspacing.
Dentro dessa nova tabela criada, insira outra tabela com 100% de largura, 0 de borda, cellpadding e cellspacing.

Na coluna(TD) da primeira tabela criada, insira o seguinte código CSS: style="background-image:url(estrela02.gif); background-repeat:no-repeat;"
Na coluna(TD) da segunda tabela criada, insira o seguinte código CSS:
style="background-image:url(estrela01.gif); background-repeat:no-repeat"

No width da segunda tabela criada, coloque o seguinte valor: width="< ? echo $x; ? >"
onde $x é o novo valor q obtemos com a regra de 3 que criamos acima.
Note que conforme a porcentagem, as estrelas coloridas sobrepõe as estrelas apagadas.

É isso galera..
Em caso de dúvidas, envie comentário q tentarei responder o mais rápido possível.
Até lá.

terça-feira, 16 de dezembro de 2008

Grid com AJAX, Paginação, Ordenação e Filtro no PHP

Estou em meio ao desenvolvimento de um site em PHP e precisei de uma Grid que além de me auxiliar na paginação, busca e ordenação dos registros, também fosse em AJAX.
Depois de procurar no site PHP Classes (http://www.phpclasses.org/) achei uma Grid bem interessante chamada EyeDBGrid.

Para download da classe, acesse o link: http://www.phpclasses.org/browse/package/4951/download/zip.html

Com ela nós conseguimos recuperar dados de qualquer base Mysql.
Segue abaixo uma imagem de exemplo:


















Após feito o download você terá as classes, imagens e folhas de estilo usadas pela Grid.
Rode o exemplo no seu servidor.
A classe utiliza as proprias funções "mysql_", sendo assim, não é preciso de ativação das classes PDO pelo servidor.

Eu não tenho muito o que falar sobre as configurações e adaptações da Grid para seu sistema, pois todos os exemplos e arquivos contendo as classes estão comentados.

Espero ter ajudado vocês.
Abraços.

segunda-feira, 15 de dezembro de 2008

Criando auditoria do seu aplicativo e melhorando o desempenho com Stored Procedures no MYSQL

E ai pessoal,
Nesse post veremos como criar uma auditoria de todas as inclusões, alterações e exclusões que os usuários fazem em sua base de Dados MySQL. Faremos isso com a ajuda de Stored Procedures que, além de nos auxiliar na auditoria, já é comprovado que elas aumentam o desempenho quando vamos trabalhar com nossas tabelas de base de dados e além disso, não ficamos dependendo de qual linguagem será implementado o sistema desde que a linguagem consiga se comunicar com a base e chamar as SPs do servidor Mysql.


ENTENDENDO O USO DE STORED PROCEDURE

Primeiramente veremos como trabalha as Stored Procedures no Mysql.
A criação de SPs estão disponíveis apenas na versão 5.x do MySQL, o que foi um marco do SGBD OpenSource. Ainda faltam alguns recursos no MYSQL como vemos em outros SGBD, como a criação de Exceptions e outras funcionalidades.

Uma Stored Procedure trabalha parecido como Functions, Procedures e Métodos das linguagens de programação, onde você cria o nome e passa os paramêtros para a rotina.
Nós temos 3 tipos diferentes de métodos em SP. Esses métodos podem ser de Entrada, de Saída ou de Entrada e Saida ao mesmo tempo.

IN: este é um parâmetro de entrada, ou seja, um parâmetro cujo seu valor será utilizado no interior do procedimento para produzir algum resultado;

OUT: esté parâmetro retorna algo de dentro do procedimento para o lado externo, colocando os valores manipulados disponíveis na memória ou no conjunto de resultados;

INOUT: faz os dois trabalhos ao mesmo tempo!


CRIANDO STORED PROCEDURES

Agora veremos como criar Stored Procedures.

Exemplo de criação de SP no MYSQL:

//Criamos um delimitador, para que possamos criar várias "linhas de código" em um único script
DELIMITER $

/*Apagamos a procedure chamada sp_pessoas caso ela já exista.*/
DROP PROCEDURE IF EXISTS sp_pessoas$

/*Criamos uma procedure chamada sp_pessoas que recebe 3 parâmetros de entrada*/
CREATE PROCEDURE sp_pessoas(IN sNome_pessoa VARCHAR(35), IN sEndereco_pessoa VARCHAR(30), IN sUf CHAR(1))
BEGIN

/*Inserimos na entidade pessoas, valores passados ao chamar a Stored Procedure*/
INSERT INTO pessoas(nome_pessoa, endereco_pessoa, uf) VALUES (sNome_pessoa, sEndereco_pessoa, sUF);

END$


Para podermos chamarmos essa StoredProcedure diretamente pelo Mysql, utilizamos a função CALL.
Exemplo:
CALL sp_pessoas('Mauricio', 'Rua sem saida', 'SP');


CRIANDO A AUDITORIA

Para criarmos a auditoria, iremos criar uma tabela chamada usabilidade, na qual irá conter dados do usuário que esta efetuando o processo no sistema e um pequeno histórico sobre o processo.
Campos da tabela usabilidade :
idoperador INT(11) - (AI, PK)
data_uso TIMESTAMP - (Default value: CURRENT_TIMESTAMP)
menu VARCHAR(35)
acao VARCHAR(35)

Agora criaremos uma SP para Adicionar uma nova ou Atualizar uma pessoa já existente na entidade.
Para isso, vamos inserir algumas linhas no código do exemplo acima. Ele ficará assim:

/*Criamos um delimitador, para que possamos criar várias "linhas de código" em um único script*/
DELIMITER $

DROP PROCEDURE IF EXISTS sp_pessoas_IU$

CREATE PROCEDURE sp_pessoas_IU(sIdpessoa INT(11), sNome_pessoa VARCHAR(35), sEndereco_pessoa VARCHAR(30), sUf CHAR(1), sIdoperador INT(11))
BEGIN

/*O que faremos aqui, é verificar pelo id da pessoa, se ela já existe. Caso nao exista (seja igual a 0), insere no banco. Caso contrário, atualiza os dados*/
IF NOT EXISTS(SELECT idpessoa FROM pessoas WHERE idpessoa = sIdpessoa) THEN
INSERT INTO pessoas(nome_pessoa, endereco_pessoa, uf) VALUES (sNome_pessoa, sEndereco_pessoa, sUF);
/*Inserimos na entidade usabilidade, o historico do que o usuário processou.*/
INSERT INTO usabilidade(idoperador, menu, acao) VALUES (sIdoperador, 'CADASTROS - PESSOAS', CONCAT('O USUÁRIO INSERIU A SEGUINTE PESSOA: ', LAST_INSERT_ID()));

ELSE

UPDATE pessoas SET nome_pessoa = sNome_pessoa, sEndereco_pessoa = sNome_pessoa, uf = sUF WHERE idpessoa = sIdpessoa;
/*Inserimos na entidade usabilidade, o historico do que o usuário processou.*/
INSERT INTO usabilidade(idoperador, menu, acao) VALUES (sIdoperador, 'CADASTROS - PESSOAS', CONCAT('O USUÁRIO ATUALIZOU A SEGUINTE PESSOA: ', sIdpessoa));

END IF;


END$


Agora, criaremos a StoredProcedure para Apagar um usuário da entidade:

DROP PROCEDURE IF EXISTS sp_pessoas_D$

CREATE PROCEDURE sp_pessoas_D(sIdpessoa INT(11), sIdoperador INT(11))
BEGIN
/*Apaga a pessoa da base de dados*/
DELETE FROM pessoas WHERE idpessoa = sIdPessoa;
/*Inserimos na entidade usabilidade, o historico do que o usuário processou.*/
INSERT INTO usabilidade(idoperador, menu, acao) VALUES (sIdoperador, 'CADASTROS - PESSOAS', CONCAT('O USUÁRIO DELETOU A SEGUINTE PESSOA: ', sIdpessoa));

END$

CHAMANDO AS STORED PROCEDURES

Stored procedure de inserção:
/*Chamamos a StoredProcedure, inserindo uma nova pessoa. Nota que o IdPessoa passado no parametro da nossa SP, tem o valor 0 (ZERO). Fazendo isso, forçamos a inserção na nossa base de dados, pois nunca existirá um usuário com o Id 0(Zero) em nossa base de dados*/

/*Insere um usuario*/
CALL sp_pessoas_IU(0, 'Mauricio', 'Rua Bahia', 'SP', 1);

/*Atualiza o usuário*/
CALL sp_pessoas_IU(1, 'Mauricio', 'Rua Bahia', 'BA', 1);

/*Deletando um usuário*/
CALL sp_pessoas_D(1, 1);


Blz pessoal?
Qualquer dúvida, enviem comentários.
Terei prazer de responder ;)

sexta-feira, 12 de dezembro de 2008

Trabalhando com tabela Mestre e Detalhe no Delphi

E aew pessoal.
Esse post foi feito pelo meu amigo Marcos Carmo. Ele esta contribuindo com esse artigo falando sobre Mestre Detalhe no Delphi utilizando componentes ADO, DataSetProvider e ClientDataSet.
Ele também disponibilizou seu e-mail para quem quizer tirar dúvidas, enviar críticas ou sugestões com relação ao artigo:
E-mail: m4rk0s@gmail.com
MSN: m4rk0s_c4rm0@hotmail.com


SITUAÇÃO
Iniciei este artigo pensando na dificuldade que inúmeros programadores têm quando trabalham com tabelas Mestre>Detalhe, inclusive eu tive essa dificuldade quando comecei a programar.
Conheci várias maneiras para resolver isso, sendo que julguei apenas 2 delas como viáveis e irei explicar o método que prefiro e utilizo.

Métodos
Aqui estão alguns métodos que encontrei com suas respectivas qualidades e defeitos.

Método 1
Iniciar transação ao abrir o form, gravar a tabela mestre, dessa maneira você obtém o ID_MESTRE e pode gravar direto na Detalhe, em caso de cancelamento basta dar um Rollback.
Qualidade: Fácil e rápido de programar
Defeito: Transação muito tempo ativa, praticamente deixa o sistema monousuário.

Método 2
Inserir o Mestre ao abrir o form e entrar em modo de edição, dessa maneira você obtém o ID_MESTRE e pode gravar direto na Detalhe, em caso de cancelamento deverá deletar os registros.
Qualidade: Fácil e rápido de programar
Defeito: É necessário apagar tudo o que foi incluido em caso de cancelamento e cada item incluido é um acesso ao banco de dados.

Método 3
Inserir os dados em um StringGrid e passar gravar tudo ao confirmar.
Qualidade: Acesso único ao banco de dados.
Defeito: Trabalhar com StringGrid é trabalhoso, exige muitas linhas de código e tempo.

Método 4
Inserir o Mestre ao criar o form e entrar em modo de edição, sendo a query detalhe utilizando o LockType como ltBatchOptimistic, dessa maneira você obtém o ID_MESTRE e pode gravar direto na Detalhe. ltBatchOptimistic serve para trabalhar em cache, caso haja cancelamento deverá deletar o registro Mestre.
Qualidade: Considero esse método viável, é rápido de programar e trabalha em cache, tendo ótimo desempenho e contato único com o banco de dados.
Defeito: É necessário apagar o registro em caso de cancelamento e se o ID_MESTRE for um campo identidade vc perde um número da sequência.

Método 5 (Vencedor)
Utilizar ClientDataSet para a tabela Detalhe, trabalhando tudo em cache e acessando o banco apenas quando a operação for finalizada.
Qualidade: Faz tudo em cache tendo ótimo desempenho e gerando o ID_MESTRE apenas na finalização da operação.
Defeito: Utiliza muitos componentes (query, datasetprovider, clientdataset) e precisa fazer looping na tabela detalhe para gravar o ID_MESTRE.


O QUE É PRECISO

1 TADOQuery (qryMestre) - para a tabela Mestre (SELECT * FROM MESTRE)
1 TADOQuery (qryDetalhe)- para a tabela Detalhe (SELECT * FROM DETALHE WHERE ID_MESTRE = :ID_MESTRE)
1 TDataSetProvider (dspDetalhe) - que será ligado a TADOQuery da tabela detalhe {dspDetalhe.DataSet := qryDetalhe}
1 TClientDataSet (cdsDetalhe) - que será ligado ao TDataSetProvider {cdsDetalhe.ProviderName := dspDetalhe.Name}
e o básico: TForm, TButton, TDBEdit, TDBLookupComboBox, TDBGrid, etc...

É importante ressaltar que para trabalhar com o TClientDataSet é necessário ter a midas.dll na pasta system32 ou na pasta do executável do software. Existem algumas pessoas que adicionam a midas.dll ao projeto, o problema de fazer isso é que o projeto fica imenso.


COMO FAZER (HOW TO)

Não vou entrar em detalhes quanto ao básico considerando que este tutorial é para quem tem um nível intermediário com Delphi.

Primeiros passos:
Antes de mais nada vá ao dspDetalhe e altere a propriedade UpdateMode para UpWhereKeyOnly, isso significa que quando o TClientDataSet montar a instrução UPDATE (em uma edição) a única coisa que entrará na cláusula WHERE será a chave da tabela (isso depende de umas configurações que faremos adiante com os fields).
Agora vamos aos fields.
Adicione-os nas Querys e no ClientDataSet (considerando as ligações que foram citadas acima).

Agora as propriedades dos fields da tabela Detalhe. A qryDetalhe e o cdsDetalhe deverão estar iguais. ProviderFlags, todos os fields do tipo fkData (exceto o ID_DETALHE se ele for Identity) deverão estar pfInUpdate = True, o ID_DETALHE deve estar com pfInWhere e pfInKey como true. Se o ID_DETALHE for Identity o delphi o trouxe como ReadOnly True, é importante que ele seja alterado para false para um procedimento que será explicado adiante. Fields do tipo Lookup, Calculated, InternalCalc ou Aggregate devem estar com todas as ProviderFlags como False.
Importante: Fields do tipo Lookup devem estar com a propriedade LookupCache = True, caso contrário você terá a mensagem 'Erro desconhecido.' e vai surtar como eu surtei um dia.

Uma informação importante, todos os trabalhos da tabela detalhe serão feitos no cdsDetalhe, então fields lookup por exemplo não precisam ser adicionados a qryDetalhe.
Clique com o botão direito no cdsDetalhe e clique na opção FetchParams (para ele pegar o parâmetro ID_MESTRE da qryDetalhe conforme exemplo acima).

Agora vamos às linhas de código!
- qryMestre.Append (ela deve estar aberta: qryMestre.Open)
Inserção:
- O parâmetro da cdsDetalhe deve estar com um ID que não exista na tabela Mestre, 0 por exemplo, se o campo for Identity ele nunca terá o valor 0. (cdsDetalhe.Params.ParamByName('ID_MESTRE').Value := 0)
Edição:
- O parâmetro da cdsDetalhe deve estar o valor do ID da tabela Mestre. (cdsDetalhe.Params.ParamByName('ID_MESTRE').Value := qryMasterID_MESTRE.Value)
Abra a cdsDetalhe agora que ela já tem o parâmetro (cdsDetalhe.Open)
Declare no seu form uma variável pública inteira inicializada com valor -1 (no meu exemplo será ID_DETALHE). A variável deve ser negativa e será decrementada para que não haja violação de chave.
o evento OnNewRecord do cdsDetalhe você deverá fazer o seguinte:
cdsDetalheID_DETALHE.Value := ID_DETALHE; //por isso a propriedade ReadOnly deve estar como false
Dec(ID_DETALHE);
- Agora trabalhe nos botões referente à tabela Detalhe:
Inserção: cdsDetalhe.Append;
Edição: cdsDetalhe.Edit;
Confirmação: cdsDetalhe.Post;
Cancelamento: cdsDetalhe.Cancel;
Exclusão: cdsDetalhe.Delete;
- No botão de finalização do processo (confirmando) deve estar um código semelhante a este:
try (opcional)
//Iniciar transação (opcional)
qryMestre.Post;
cdsDetalhe.DisableControls; //Apenas para aumentar a performance do looping que será feito.
cdsDetalhe.First;
while not cdsDetalhe.Eof do
begin
cdsDetalhe.Edit;
cdsDetalheID_MESTRE.Value := qryMestreID_MESTRE.Value;
cdsDetalhe.Post;
cdsDetalhe.Next;
end;
cdsDetalhe.EnableControls;
if cdsDetalhe.ApplyUpdates(0) <> 0 then //Caso haja um erro ao incluir os itens ele gera uma exceção > 0 não applica atualizações com erros > -1 ignora erros e qualquer valor maior que 0 é a quantidade de erros que ele irá permitir
Raise Exception.Create('Erro ao incluir os itens (cdsDetalhe.ApplyUpdates(0))')
//commit (opcional)
except (opcional)
on e:Exception do
begin
//Retorna transação (rollback)
Application.MessageBox(pChar('Erro: '+e.Message),'Atenção',MB_ICONERROR);
end;


Bom.. é isso aí...
Não sou muito bom para explicar, mas acho que da pra entender.
Qualquer dúvida basta me contatar por e-mail.