Here is what I do at my current place, it is not yet ideal but is a good starting point:
Developer's code is ready, it gets manually imported to Test for verification via XPOs. Once functional testing is done, developer checks in the code in his workspace to TFS Dev branch.
I do the code review and merge/migrate the code from Dev branch to Prod branch (so it only exists in TFS at the moment) on my local computer within Visual Studio Team Explorer.
I log on to the Build instance that is hooked up against the Prod TFS branch, and do a Version control Synchronize, which fetches the code that has been reviewed and ready to go to Production, and imports it to AX from the repository on it's own, then I close the AX client.
I start the automated build process (manually, or scheduled) in VS Team Explorer, that relies on the TFS Activity Workflow and some of the publicly available tools made by fellow community members (CodeCrib library), which produces the AXModelStore in the end.
I release that with a PowerShell script that into our Staging/QA environment for final verification, and after it is marked as ready to go, during the Production maintenance window again we release the modelstore using PowerShell, then do the manual steps (DB synch, AIF ports refresh, update jobs, setup changes, whatsoever required).
Later on if I have some time, we will change this with the team to have a Build environment separately for Test, which will automatically start the AX client and do a VCS synchronize, then I plan to use either the Release Management that Martin has mentioned to deploy the modelstore to Test, or might go down the PowerShell script route again. Same improvement will be done for release to Staging. I deliberately want to keep the requirement to manually synchronize Prod Build, so I have control as gatekeeper over what is really going out, as a second level verification for the synchronized objects' list.
Ultimately your Staging/QA and Production environment's code will be identical code/objects-wise, since that is what you have in the modelstore. Their ID's will match, which will make your life easier by not having ID conflicts left and right for your modelstore and SQLDictionary table.